name implementation-lokstra-generate-http-files description Generate .http client files for testing API endpoints. Create executable HTTP requests with environment variables, request/response examples, and curl equivalents. Use after API specification is finalized for quick endpoint testing. phase implementation order 6 license MIT compatibility {"lokstra_version":">=0.1.0","go_version":">=1.18"}
Implementation: Generate HTTP Client Files
When to Use
Use this skill when:
Creating executable HTTP requests for endpoint testing
Documenting API usage with live examples
Setting up development testing without external tools
Sharing API examples with team
Testing validation rules and error handling
Verifying multi-tenant isolation
Prerequisites:
✅ API specification finalized (see: design-lokstra-api-specification)
✅ Handler implementation complete (see: implementation-lokstra-create-handler)
✅ Application running locally or on server
✅ Environment variables configured
✅ Database migrated and seeded (optional)
Next Steps After This Skill :
Write unit tests (see: advanced-lokstra-tests)
Validate consistency (see: advanced-lokstra-validate-consistency)
Overview
This skill generates .http files (VS Code REST Client format) for:
Complete CRUD Testing - All endpoints per module
Validation Testing - Error cases and edge conditions
Authentication Testing - JWT, API keys, Bearer tokens
Multi-Tenant Testing - Tenant isolation verification
Workflow Testing - Sequential operations with response capture
Performance Testing - Pagination, filtering, sorting
Output Location : api/{module}.http or test/{module}.http
Tool Requirements
VS Code extension: REST Client
Install via VS Code Extensions panel or command line:
code --install-extension humao.rest-client
HTTP File Basics
Simple GET Request
File: api/user.http
### Get user by ID
GET http://localhost:8080/api/users/550e8400-e29b-41d4-a716-446655440000
Accept: application/json
POST with Request Body
### Create user
POST http://localhost:8080/api/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}
Path Parameters and Variables
@baseUrl = http://localhost:8080
@userId = 550e8400-e29b-41d4-a716-446655440000
### Get user
GET {{baseUrl}}/api/users/{{userId}}
### Update user
PUT {{baseUrl}}/api/users/{{userId}}
Content-Type: application/json
{
"name": "Jane Doe",
"email": "jane@example.com"
}
### Delete user
DELETE {{baseUrl}}/api/users/{{userId}}
Environment Configuration
File: .http-client.env.json
{
"development" : {
"baseUrl" : "http://localhost:8080" ,
"userId" : "550e8400-e29b-41d4-a716-446655440000" ,
"authToken" : "dev-jwt-token"
} ,
"production" : {
"baseUrl" : "https://api.myapp.com" ,
"userId" : "550e8400-e29b-41d4-a716-446655440000" ,
"authToken" : "prod-jwt-token"
}
}
Using Environment Variables
### Using variables
GET {{baseUrl}}/api/users/{{userId}}
Authorization: Bearer {{authToken}}
Complete API Test Suite
File: api/users.http
@baseUrl = http://localhost:8080
@contentType = application/json
### ============================================
### USER API - Test Suite
### ============================================
### 1. Create User (POST)
POST {{baseUrl}}/api/users
Content-Type: {{contentType}}
{
"name": "John Doe",
"email": "john@example.com"
}
> {%
client.global.set("userId", response.body.id);
client.log("Saved userId: " + client.global.get("userId"));
%}
###
### 2. Get User (GET)
GET {{baseUrl}}/api/users/{{userId}}
Accept: {{contentType}}
###
### 3. Update User (PUT)
PUT {{baseUrl}}/api/users/{{userId}}
Content-Type: {{contentType}}
{
"name": "Jane Doe",
"email": "jane@example.com"
}
###
### 4. Delete User (DELETE)
DELETE {{baseUrl}}/api/users/{{userId}}
###
### 5. List Users (GET)
GET {{baseUrl}}/api/users
Accept: {{contentType}}
Authentication Patterns
Bearer Token
@baseUrl = http://localhost:8080
@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
### Authenticated request
GET {{baseUrl}}/api/users/profile
Authorization: Bearer {{token}}
Basic Auth
### Basic authentication
GET {{baseUrl}}/api/users
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
API Key
### API key in header
GET {{baseUrl}}/api/users
X-API-Key: your-api-key-here
Login and Get Token
@baseUrl = http://localhost:8080
### 1. Login
POST {{baseUrl}}/api/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}
> {%
client.global.set("token", response.body.token);
%}
###
### 2. Use token in subsequent requests
GET {{baseUrl}}/api/users/profile
Authorization: Bearer {{token}}
Request/Response Examples
With Headers
### Request with custom headers
POST {{baseUrl}}/api/users
Content-Type: application/json
Accept: application/json
X-Request-ID: req-12345
X-Client-Version: 1.0.0
{
"name": "John Doe",
"email": "john@example.com"
}
Query Parameters
@baseUrl = http://localhost:8080
### List with pagination
GET {{baseUrl}}/api/users?page=1&limit=10&sort=created_at&order=desc
###
### Filter by status
GET {{baseUrl}}/api/users?status=active&role=admin
File Upload
### Upload file
POST {{baseUrl}}/api/users/{{userId}}/avatar
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="avatar.jpg"
Content-Type: image/jpeg
< ./avatar.jpg
------WebKitFormBoundary--
Response Handling
Capture Values from Response
### Create and capture ID
POST {{baseUrl}}/api/users
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com"
}
> {%
// Extract and save response fields
client.global.set("userId", response.body.id);
client.global.set("email", response.body.email);
// Log to console
client.log("Created user: " + response.body.id);
// Assert response status
client.assert(response.status === 201, "Expected 201 Created");
%}
###
### Use captured value
GET {{baseUrl}}/api/users/{{userId}}
Response Assertions
### Test response
GET {{baseUrl}}/api/users/{{userId}}
> {%
// Check status code
client.assert(response.status === 200, "Expected 200 OK");
// Check response body
client.assert(response.body.id !== null, "User ID should not be null");
client.assert(response.body.email === "john@example.com", "Email mismatch");
// Check headers
client.assert(response.headers.valueOf("content-type").includes("application/json"), "Invalid content type");
// Log results
client.log("All assertions passed!");
%}
Module-Organized Structure
api/
├── user.http # User endpoints
├── auth.http # Authentication endpoints
├── order.http # Order endpoints
├── notification.http # Notification endpoints
├── variables.http # Shared variables
└── .http-client.env.json # Environment configs
File: api/variables.http
### Shared variables and test data
@baseUrl = http://localhost:8080
@contentType = application/json
@testUserName = John Doe
@testUserEmail = john@example.com
@adminToken = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
@userToken = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Curl Equivalents
Each .http file can be converted to curl:
### Create user
POST {{baseUrl}}/api/users
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "John Doe",
"email": "john@example.com"
}
Curl equivalent:
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN " \
-d '{"name":"John Doe","email":"john@example.com"}'
Multi-Tenant Testing
For Lokstra applications with multi-tenant architecture, include tenant-specific headers and validation.
Multi-Tenant Environment Configuration
File: .http-client.env.json
{
"tenant_a" : {
"baseUrl" : "http://localhost:3000" ,
"tenantId" : "tenant_a" ,
"adminToken" : "eyJhbGc..._tenant_a_admin" ,
"userToken" : "eyJhbGc..._tenant_a_user"
} ,
"tenant_b" : {
"baseUrl" : "http://localhost:3000" ,
"tenantId" : "tenant_b" ,
"adminToken" : "eyJhbGc..._tenant_b_admin" ,
"userToken" : "eyJhbGc..._tenant_b_user"
}
}
Tenant-Isolated Requests
File: api/tenant-users.http
@baseUrl = http://localhost:3000
@contentType = application/json
@tenantId = tenant_a
### ============================================
### MULTI-TENANT USER API - Test Suite
### ============================================
### 1. Create User for Tenant A
POST {{baseUrl}}/api/users
Content-Type: {{contentType}}
Authorization: Bearer {{adminToken}}
X-Tenant-ID: {{tenantId}}
{
"name": "John Doe",
"email": "john@tenant-a.com"
}
> {%
client.global.set("tenantAUserId", response.body.data.id);
client.assert(response.status === 201, "Expected 201 Created");
client.assert(response.body.data.tenant_id === "tenant_a", "Tenant ID mismatch");
%}
###
### 2. Verify Tenant Isolation - Tenant B cannot access Tenant A's user
GET {{baseUrl}}/api/users/{{tenantAUserId}}
Authorization: Bearer {{tenant_b_token}}
X-Tenant-ID: tenant_b
> {%
// Should return 403 Forbidden or 404 Not Found
client.assert(response.status === 403 || response.status === 404,
"Expected tenant isolation - 403 or 404");
%}
###
### 3. List Users - Should only return current tenant's users
GET {{baseUrl}}/api/users
Authorization: Bearer {{adminToken}}
X-Tenant-ID: {{tenantId}}
> {%
client.assert(response.status === 200, "Expected 200 OK");
// All returned users should belong to current tenant
const users = response.body.data || [];
users.forEach(user => {
client.assert(user.tenant_id === client.global.get("tenantId"),
"User from different tenant found!");
});
%}
Validation Testing
Test all validation rules defined in your DTOs.
Validation Test Cases
File: api/users-validation.http
@baseUrl = http://localhost:3000
@contentType = application/json
### ============================================
### USER VALIDATION TESTS
### ============================================
### Test: Missing required field (name)
# Expected: 400 Bad Request with validation error
POST {{baseUrl}}/api/users
Content-Type: {{contentType}}
{
"email": "john@example.com"
}
> {%
client.assert(response.status === 400, "Expected 400 for missing required field");
client.assert(response.body.message.includes("validation") ||
response.body.error.includes("required"),
"Expected validation error message");
%}
###
### Test: Invalid email format
# Expected: 400 Bad Request with email validation error
POST {{baseUrl}}/api/users
Content-Type: {{contentType}}
{
"name": "John Doe",
"email": "not-an-email"
}
> {%
client.assert(response.status === 400, "Expected 400 for invalid email");
%}
###
### Test: Name too short (min=3)
# Expected: 400 Bad Request
POST {{baseUrl}}/api/users
Content-Type: {{contentType}}
{
"name": "Jo",
"email": "john@example.com"
}
> {%
client.assert(response.status === 400, "Expected 400 for name too short");
%}
###
### Test: Name too long (max=50)
# Expected: 400 Bad Request
POST {{baseUrl}}/api/users
Content-Type: {{contentType}}
{
"name": "This name is way too long and exceeds the maximum allowed characters for a user name",
"email": "john@example.com"
}
> {%
client.assert(response.status === 400, "Expected 400 for name too long");
%}
###
### Test: Duplicate email (unique constraint)
# Expected: 409 Conflict or 400 Bad Request
POST {{baseUrl}}/api/users
Content-Type: {{contentType}}
{
"name": "Jane Doe",
"email": "existing@example.com"
}
> {%
client.assert(response.status === 409 || response.status === 400,
"Expected conflict or bad request for duplicate email");
%}
###
### Test: Invalid status value (oneof validation)
# Expected: 400 Bad Request
PUT {{baseUrl}}/api/users/{{userId}}
Content-Type: {{contentType}}
{
"name": "John Doe",
"status": "invalid_status"
}
> {%
client.assert(response.status === 400, "Expected 400 for invalid enum value");
%}
Error Response Testing
Test all error scenarios and HTTP status codes.
Error Cases Test Suite
File: api/users-errors.http
@baseUrl = http://localhost:3000
@contentType = application/json
### ============================================
### ERROR RESPONSE TESTS
### ============================================
### 400 Bad Request - Malformed JSON
POST {{baseUrl}}/api/users
Content-Type: {{contentType}}
{invalid json}
> {%
client.assert(response.status === 400, "Expected 400 for malformed JSON");
%}
###
### 401 Unauthorized - Missing token
GET {{baseUrl}}/api/users/profile
> {%
client.assert(response.status === 401, "Expected 401 for missing auth");
%}
###
### 401 Unauthorized - Invalid token
GET {{baseUrl}}/api/users/profile
Authorization: Bearer invalid-token-here
> {%
client.assert(response.status === 401, "Expected 401 for invalid token");
%}
###
### 403 Forbidden - Insufficient permissions
DELETE {{baseUrl}}/api/admin/users/{{userId}}
Authorization: Bearer {{userToken}}
> {%
client.assert(response.status === 403, "Expected 403 for insufficient permissions");
%}
###
### 404 Not Found - Non-existent resource
GET {{baseUrl}}/api/users/non-existent-uuid-here
> {%
client.assert(response.status === 404, "Expected 404 for non-existent resource");
%}
###
### 405 Method Not Allowed
PATCH {{baseUrl}}/api/health
Content-Type: {{contentType}}
{}
> {%
client.assert(response.status === 405 || response.status === 404,
"Expected 405 Method Not Allowed or 404");
%}
###
### 415 Unsupported Media Type
POST {{baseUrl}}/api/users
Content-Type: text/plain
name=John&email=john@example.com
> {%
client.assert(response.status === 415 || response.status === 400,
"Expected 415 or 400 for wrong content type");
%}
Pagination and Filtering Tests
Test query parameter handling for list endpoints.
Pagination Test Cases
File: api/users-pagination.http
@baseUrl = http://localhost:3000
@contentType = application/json
### ============================================
### PAGINATION & FILTERING TESTS
### ============================================
### Default pagination
GET {{baseUrl}}/api/users
Accept: {{contentType}}
> {%
client.assert(response.status === 200, "Expected 200 OK");
client.assert(response.body.meta.page === 1, "Default page should be 1");
client.assert(response.body.meta.limit <= 20, "Default limit should be <= 20");
%}
###
### Custom page and limit
GET {{baseUrl}}/api/users?page=2&limit=5
Accept: {{contentType}}
> {%
client.assert(response.status === 200, "Expected 200 OK");
client.assert(response.body.meta.page === 2, "Page should be 2");
client.assert(response.body.meta.limit === 5, "Limit should be 5");
%}
###
### Filter by status
GET {{baseUrl}}/api/users?status=active
Accept: {{contentType}}
> {%
client.assert(response.status === 200, "Expected 200 OK");
const users = response.body.data || [];
users.forEach(user => {
client.assert(user.status === "active", "All users should be active");
});
%}
###
### Sort by created_at descending
GET {{baseUrl}}/api/users?sort=created_at&order=desc
Accept: {{contentType}}
> {%
client.assert(response.status === 200, "Expected 200 OK");
%}
###
### Search by name
GET {{baseUrl}}/api/users?q=John
Accept: {{contentType}}
> {%
client.assert(response.status === 200, "Expected 200 OK");
%}
###
### Invalid pagination parameters
GET {{baseUrl}}/api/users?page=-1&limit=1000
Accept: {{contentType}}
> {%
// Should either normalize values or return 400
client.assert(response.status === 200 || response.status === 400,
"Expected 200 (normalized) or 400 (validation error)");
%}
Complete Module Template
Use this template when generating .http files for a new module.
Template: api/{module}.http
### ============================================
### {MODULE_NAME} API - Test Suite
### Generated from API Specification
### ============================================
@baseUrl = http://localhost:3000
@contentType = application/json
@authToken = {{$dotenv AUTH_TOKEN}}
### ============================================
### HEALTH CHECK
### ============================================
### Health check
GET {{baseUrl}}/health
> {%
client.assert(response.status === 200, "Health check failed");
%}
###
### ============================================
### CRUD OPERATIONS
### ============================================
### 1. CREATE - POST /{resource}
# @name create{Resource}
POST {{baseUrl}}/api/{resources}
Content-Type: {{contentType}}
Authorization: Bearer {{authToken}}
{
"field1": "value1",
"field2": "value2"
}
> {%
client.global.set("{resource}Id", response.body.data.id);
client.assert(response.status === 201, "Expected 201 Created");
%}
###
### 2. READ - GET /{resource}/{id}
GET {{baseUrl}}/api/{resources}/{{{resource}Id}}
Accept: {{contentType}}
Authorization: Bearer {{authToken}}
> {%
client.assert(response.status === 200, "Expected 200 OK");
client.assert(response.body.data.id === client.global.get("{resource}Id"), "ID mismatch");
%}
###
### 3. LIST - GET /{resources}
GET {{baseUrl}}/api/{resources}?page=1&limit=10
Accept: {{contentType}}
Authorization: Bearer {{authToken}}
> {%
client.assert(response.status === 200, "Expected 200 OK");
client.assert(Array.isArray(response.body.data), "Expected array of {resources}");
%}
###
### 4. UPDATE - PUT /{resource}/{id}
PUT {{baseUrl}}/api/{resources}/{{{resource}Id}}
Content-Type: {{contentType}}
Authorization: Bearer {{authToken}}
{
"field1": "updated_value1",
"field2": "updated_value2"
}
> {%
client.assert(response.status === 200, "Expected 200 OK");
%}
###
### 5. DELETE - DELETE /{resource}/{id}
DELETE {{baseUrl}}/api/{resources}/{{{resource}Id}}
Authorization: Bearer {{authToken}}
> {%
client.assert(response.status === 200 || response.status === 204,
"Expected 200 or 204");
%}
###
### ============================================
### VALIDATION TESTS
### ============================================
### Validation: Missing required field
POST {{baseUrl}}/api/{resources}
Content-Type: {{contentType}}
Authorization: Bearer {{authToken}}
{
"field2": "value2"
}
> {%
client.assert(response.status === 400, "Expected 400 for missing required field");
%}
###
### ============================================
### ERROR CASES
### ============================================
### Error: Not Found
GET {{baseUrl}}/api/{resources}/non-existent-id
Authorization: Bearer {{authToken}}
> {%
client.assert(response.status === 404, "Expected 404 Not Found");
%}
###
### Error: Unauthorized
GET {{baseUrl}}/api/{resources}
> {%
client.assert(response.status === 401, "Expected 401 Unauthorized");
%}
Testing Workflow
Step 1: Start Application
go run .
Step 2: Open HTTP File
Open api/{module}.http in VS Code with REST Client extension
Step 3: Execute Requests
Click "Send Request" above each request block
Step 4: Review Responses
Check status code (shown in response header)
Verify response body matches expectations
Review response headers
Step 5: Capture Values
Use response handlers (> {% ... %}) to save values for chained requests
Step 6: Run Full Test Suite
Execute all requests sequentially to test complete workflow
AI Agent Instructions
When generating HTTP files, follow this process:
Step 1: Analyze API Specification (Required)
Read the API specification document to understand:
All endpoints (method, path, description)
Request body schemas with validation rules
Response schemas and status codes
Authentication requirements
Multi-tenant requirements (X-Tenant-ID header)
Step 2: Create File Structure
api/
├── {module}.http # Main CRUD tests
├── {module}-validation.http # Validation test cases
├── {module}-errors.http # Error response tests
└── .http-client.env.json # Environment configuration
Step 3: Generate Content
For each endpoint in the API specification:
Create basic request with all required headers
Add request body matching DTO schema
Add response assertions for expected status code
Add response capture for dynamic values (IDs, tokens)
Add descriptive comment explaining the test
Step 4: Include Test Categories
Ensure coverage of:
Category Required Tests CRUD Create, Read, Update, Delete, List Validation Missing required fields, invalid formats, boundary values Auth Missing token, invalid token, expired token Permissions Forbidden actions, role-based access Multi-tenant Tenant isolation, cross-tenant access denied Pagination Default values, custom page/limit, invalid params Search/Filter Query params, sorting, filtering
Step 5: Naming Conventions
### [Action] [Resource] [Condition]
### Create User with valid data
### Get User by ID
### Update User with invalid email
### Delete User without permission
### List Users with pagination
DO's and DON'Ts
DO's ✅
Include descriptive comments for every request
Add response assertions for critical validations
Use variables for reusable values (baseUrl, tokens)
Test both success and error scenarios
Capture dynamic values (IDs) for sequential requests
Match validation rules from DTOs (min, max, required)
Include multi-tenant headers when applicable
Use environment files for sensitive data
DON'Ts ❌
Hardcode authentication tokens in files
Skip error case testing
Ignore multi-tenant isolation tests
Forget to add Content-Type headers
Use invalid/placeholder UUIDs without noting
Skip pagination boundary tests
Ignore validation constraint testing
Leave requests without descriptions
Best Practices
1. Organize by Module
api/
├── auth.http # Authentication endpoints
├── users.http # User CRUD + profile
├── users-validation.http # User validation tests
├── orders.http # Order management
└── products.http # Product catalog
2. Use Descriptive Request Names
### ✅ Good - Clear and descriptive
### Create new user with email validation
POST {{baseUrl}}/api/users
### ❌ Bad - No description
POST {{baseUrl}}/api/users
3. Document Test Scenarios
### Create new user with validation
# Tests:
# - Required fields: name, email
# - Email format: validate:"email"
# - Name length: min=3, max=50
# Expected: 201 Created with user object
POST {{baseUrl}}/api/users
4. Keep Sensitive Data in Environment
{
"development" : {
"adminToken" : "dev-token-here" ,
"dbPassword" : "never-commit-this"
}
}
Important : Add .http-client.env.json to .gitignore!
5. Group Related Tests
### ============================================
### USER CRUD OPERATIONS
### ============================================
### Create User
...
### Get User
...
### ============================================
### USER VALIDATION TESTS
### ============================================
### Invalid email format
...
Summary Checklist
When generating HTTP files for a module:
File Structure
Test Coverage
All CRUD endpoints (Create, Read, Update, Delete, List)
All validation rules from DTOs tested
All error status codes (400, 401, 403, 404, 409, 500)
Authentication scenarios (valid, invalid, missing)
Authorization scenarios (permitted, forbidden)
Pagination and filtering
Multi-tenant isolation (if applicable)
Request Quality
Variables and Environment
Documentation
Next Steps
After generating HTTP files:
Run Application : go run .
Execute Test Suite : Run all requests in sequence
Verify Responses : Check status codes and body content
Document Issues : Note any failures for handler fixes
Create Unit Tests : See advanced-lokstra-tests
Validate Consistency : See advanced-lokstra-validate-consistency
Related Skills
Last Updated : 2024-01-20
Version : 2.0.0
Phase : Implementation
Previous Skill : implementation-lokstra-create-migrations
Next Skill : advanced-lokstra-tests