APIs
REST APIs and OpenAPI integration
Overview
This project exposes REST APIs through automatic OpenAPI integration with tRPC. Any tRPC procedure can be exposed as a REST endpoint with proper configuration, enabling external clients to consume your API without using tRPC.
Key Features
- OpenAPI Specification - Automatic generation from tRPC procedures
- Type-Safe REST Endpoints - Maintained type safety from server to client
- Authentication - Bearer token (API key) authentication
- Rate Limiting - Built-in rate limiting per endpoint
- Documentation - Auto-generated API documentation
- Swagger UI - Interactive API explorer
Architecture
The API layer is built on top of tRPC with the following components:
┌─────────────────┐
│ External Client │
└────────┬────────┘
│ HTTP Request
│ (Bearer Token)
↓
┌─────────────────┐
│ OpenAPI Layer │ ← trpc-to-openapi
└────────┬────────┘
│
↓
┌─────────────────┐
│ tRPC Router │ ← Type-safe procedures
└────────┬────────┘
│
↓
┌─────────────────┐
│ Database │
└─────────────────┘Enabling REST Endpoints
To expose a tRPC procedure as a REST endpoint, add OpenAPI metadata to the procedure definition:
import { authProcedure } from "@/trpc/procedures/trpc";
import { z } from "zod";
export const apiKeyRouter = createTRPCRouter({
list: authProcedure
.meta({
rateLimit: "QUERY",
openapi: {
enabled: true,
method: "GET",
path: "/api-keys",
tags: ["API Keys"],
summary: "List API keys",
description: "Returns all API keys for the authenticated user",
protect: true, // Requires authentication
},
})
.input(z.object({}))
.output(z.object({
success: z.boolean(),
payload: z.array(apiKeySchema),
}))
.query(async ({ ctx }) => {
const keys = await getByUserId(ctx.auth.user.id);
return {
success: true,
payload: keys,
};
}),
});OpenAPI procedures require explicit .input() and .output() schemas for proper API documentation generation.
REST Endpoint Structure
All REST endpoints are available at:
https://your-app.com/api/<path>Where <path> is defined in the OpenAPI metadata of each procedure.
Example Endpoints
| Endpoint | Method | Description | Auth Required |
|---|---|---|---|
/api/api-keys | GET | List user's API keys | Yes |
/api/api-keys | POST | Create new API key | Yes |
/api/api-keys/:id | DELETE | Delete API key | Yes |
/api/health | GET | Health check | No |
Authentication
Protected endpoints require authentication via Bearer token in the Authorization header.
Getting an API Key
Users can generate API keys through the dashboard:
- Navigate to
/dashboard/api-keys - Click "Create API Key"
- Copy the generated key
- Use the key in API requests
Using Bearer Token
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://your-app.com/api/api-keysPublic Endpoints
Some endpoints may be public (no authentication required) by setting protect: false in the OpenAPI metadata:
healthCheck: publicProcedure
.meta({
openapi: {
enabled: true,
method: "GET",
path: "/health",
protect: false, // No auth required
tags: ["System"],
summary: "Health check",
},
})
.input(z.object({}))
.output(z.object({ status: z.string() }))
.query(() => ({ status: "ok" }))Request/Response Format
Request Headers
Authorization: Bearer YOUR_API_KEY
Content-Type: application/jsonSuccess Response
All endpoints return the standard ActionResponse format:
{
"success": true,
"message": "Operation completed successfully",
"payload": {
"id": "123",
"name": "My Resource"
}
}Error Response
{
"success": false,
"message": "Validation failed",
"errors": [
{
"path": ["name"],
"message": "Name is required"
}
]
}Rate Limiting
All API endpoints are rate-limited based on the procedure metadata:
.meta({
rateLimit: "QUERY", // or "MUTATION"
// ... openapi config
})Rate limits:
- QUERY: 100 requests per minute
- MUTATION: 20 requests per minute
Exceeded limits return HTTP 429 with:
{
"success": false,
"message": "Rate limit exceeded. Try again later."
}OpenAPI Specification
The full OpenAPI specification is automatically generated and available at:
https://your-app.com/api/openapi.jsonViewing Documentation
Interactive API documentation (Swagger UI) is available during development at:
http://localhost:3000/api/docsThe Swagger UI is only available in development mode. In production, clients should use the OpenAPI JSON spec to generate their own documentation.
Error Handling
The API layer uses consistent error handling:
| HTTP Status | Description |
|---|---|
200 | Success |
400 | Bad request / Validation |
401 | Unauthorized |
403 | Forbidden |
404 | Not found |
429 | Rate limit exceeded |
500 | Internal server error |
Errors include detailed messages and validation errors when applicable:
{
"success": false,
"message": "Validation failed",
"errors": [
{
"path": ["email"],
"message": "Invalid email format"
},
{
"path": ["age"],
"message": "Must be at least 18"
}
]
}Testing REST Endpoints
Using cURL
# GET request
curl -X GET \
-H "Authorization: Bearer YOUR_API_KEY" \
https://your-app.com/api/api-keys
# POST request
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "My API Key"}' \
https://your-app.com/api/api-keysUsing Postman
- Set method and URL
- Add header:
Authorization: Bearer YOUR_API_KEY - Set body (for POST/PUT): JSON format
- Send request
Using JavaScript/TypeScript
const response = await fetch("https://your-app.com/api/api-keys", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
});
const data = await response.json();Next Steps
- OpenAPI Configuration - Detailed OpenAPI setup
- REST Endpoints - Using REST endpoints
- tRPC Procedures - Creating procedures
- Authentication - Authentication flows