Files
2025-11-30 08:25:20 +08:00

7.9 KiB

API Endpoints: [Project Name]

Base URL: /api Framework: Hono (Cloudflare Workers) Auth: Clerk JWT with custom template Validation: Zod schemas Last Updated: [Date]


Overview

All API endpoints follow RESTful conventions and return JSON responses.

Base URL:

  • Local dev: http://localhost:5173/api
  • Production: https://[your-domain].workers.dev/api

Authentication: Most endpoints require a valid Clerk JWT in the Authorization header:

Authorization: Bearer <jwt_token>

Content Type: All requests and responses use application/json


Response Format

Success Response

{
  "data": { /* response data */ },
  "meta": { /* optional metadata like pagination */ }
}

Error Response

{
  "error": "Human-readable error message",
  "code": "ERROR_CODE",
  "details": { /* optional additional context */ }
}

Standard Error Codes:

  • VALIDATION_ERROR (400): Request body failed validation
  • UNAUTHORIZED (401): Missing or invalid JWT
  • FORBIDDEN (403): Valid JWT but insufficient permissions
  • NOT_FOUND (404): Resource doesn't exist
  • INTERNAL_ERROR (500): Server error

Authentication Endpoints

POST /api/auth/verify

Purpose: Verify JWT token validity Auth: None (public endpoint)

Request Body:

{
  "token": "string"
}

Response 200:

{
  "data": {
    "valid": true,
    "email": "user@example.com",
    "userId": "clerk_user_123"
  }
}

Response 401:

{
  "error": "Invalid or expired token",
  "code": "UNAUTHORIZED"
}

Validation:

const schema = z.object({
  token: z.string().min(1)
})

GET /api/auth/me

Purpose: Get current authenticated user's profile Auth: Required

Response 200:

{
  "data": {
    "id": 1,
    "email": "user@example.com",
    "displayName": "John Doe",
    "avatarUrl": "https://r2.../avatar.jpg",
    "createdAt": 1234567890
  }
}

Response 401:

{
  "error": "Not authenticated",
  "code": "UNAUTHORIZED"
}

[Resource] Endpoints

GET /api/[resource]

Purpose: List all [resources] for authenticated user Auth: Required

Query Parameters:

  • limit (optional): Number of items to return (default: 50, max: 100)
  • offset (optional): Number of items to skip (default: 0)
  • sort (optional): Sort field (default: created_at)
  • order (optional): Sort order - asc or desc (default: desc)

Example: /api/[resource]?limit=20&offset=0&sort=created_at&order=desc

Response 200:

{
  "data": [
    {
      "id": 1,
      "userId": 1,
      "[field]": "value",
      "createdAt": 1234567890,
      "updatedAt": 1234567890
    }
  ],
  "meta": {
    "total": 42,
    "limit": 20,
    "offset": 0
  }
}

Response 401: Not authenticated


GET /api/[resource]/:id

Purpose: Get a specific [resource] by ID Auth: Required

Response 200:

{
  "data": {
    "id": 1,
    "userId": 1,
    "[field]": "value",
    "createdAt": 1234567890,
    "updatedAt": 1234567890
  }
}

Response 404:

{
  "error": "[Resource] not found",
  "code": "NOT_FOUND"
}

Response 403: User doesn't own this resource


POST /api/[resource]

Purpose: Create a new [resource] Auth: Required

Request Body:

{
  "[field1]": "value",
  "[field2]": "value"
}

Validation:

const schema = z.object({
  [field1]: z.string().min(1).max(100),
  [field2]: z.string().optional(),
  // ... other fields
})

Response 201:

{
  "data": {
    "id": 42,
    "userId": 1,
    "[field1]": "value",
    "[field2]": "value",
    "createdAt": 1234567890,
    "updatedAt": 1234567890
  }
}

Response 400 (validation error):

{
  "error": "Validation failed",
  "code": "VALIDATION_ERROR",
  "details": {
    "field1": "Must be at least 1 character"
  }
}

PATCH /api/[resource]/:id

Purpose: Update an existing [resource] Auth: Required

Request Body (all fields optional):

{
  "[field1]": "new value",
  "[field2]": "new value"
}

Validation:

const schema = z.object({
  [field1]: z.string().min(1).max(100).optional(),
  [field2]: z.string().optional(),
}).refine(data => Object.keys(data).length > 0, {
  message: "At least one field must be provided"
})

Response 200:

{
  "data": {
    "id": 42,
    "userId": 1,
    "[field1]": "new value",
    "[field2]": "new value",
    "createdAt": 1234567890,
    "updatedAt": 1234567999
  }
}

Response 404: Resource not found Response 403: User doesn't own this resource


DELETE /api/[resource]/:id

Purpose: Delete a [resource] Auth: Required

Response 204: No content (success)

Response 404: Resource not found Response 403: User doesn't own this resource


Middleware

CORS Middleware

Applies to: All routes Headers:

Access-Control-Allow-Origin: https://[your-domain].com
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

Auth Middleware

Applies to: All routes except public endpoints Validates: Clerk JWT in Authorization header Adds to context: c.get('userId'), c.get('email') Rejects: Missing, invalid, or expired tokens (401)

Error Handler Middleware

Applies to: All routes Catches: Unhandled errors Returns: 500 with sanitized error message Logs: Full error details for debugging

Validation Middleware

Applies to: POST and PATCH routes Validates: Request body against Zod schema Returns: 400 with field-specific errors if validation fails


Rate Limiting

[Optional: Define rate limits if applicable]

Default Limits:

  • Anonymous requests: 10 requests per minute
  • Authenticated requests: 100 requests per minute

Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1234567890

Webhook Endpoints

[Optional: Document webhooks if applicable]

POST /api/webhooks/[service]

Purpose: Handle webhook from [third-party service] Auth: Webhook signature verification Source: [Service name]

Expected Payload:

{
  "event": "event_type",
  "data": { /* event data */ }
}

Response 200: Webhook processed successfully Response 400: Invalid signature or payload


Testing

Manual Testing with curl

Create resource:

curl -X POST http://localhost:5173/api/[resource] \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -d '{"field1": "value"}'

Get resource:

curl http://localhost:5173/api/[resource]/1 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Update resource:

curl -X PATCH http://localhost:5173/api/[resource]/1 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -d '{"field1": "new value"}'

Delete resource:

curl -X DELETE http://localhost:5173/api/[resource]/1 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Getting a Test JWT

Use Clerk's development mode or sign in to get a token:

// In browser console after logging in
const token = await window.Clerk.session.getToken();
console.log(token);

Deployment

Wrangler Configuration: Ensure wrangler.jsonc includes necessary bindings:

{
  "vars": {
    "CLERK_PUBLISHABLE_KEY": "pk_test_..."
  },
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "[your-database]",
      "database_id": "[database-id]"
    }
  ]
}

Environment Variables (set via dashboard or CLI):

  • CLERK_SECRET_KEY: Secret key for JWT verification

Future Endpoints

Planned endpoints to implement:

  • /api/[feature] - [Description]
  • /api/[other-feature] - [Description]

Revision History

v1.0 ([Date]): Initial API design v1.1 ([Date]): [Changes made]