Skip to content

API Backend

Build a production-ready REST API with authentication, validation, rate limiting, and comprehensive error handling.

  • RESTful API design
  • JWT authentication
  • Input validation with Zod
  • Rate limiting
  • Error handling
  • API documentation
  • CORS support
  • Directoryapp/
    • Directoryapi/
      • middleware.ts # API-wide middleware
      • Directoryv1/
        • middleware.ts # Version middleware
        • Directoryusers/
          • route.ts # GET, POST /api/v1/users
          • Directory[id]/
            • route.ts # GET, PUT, DELETE /api/v1/users/:id
        • Directoryposts/
          • route.ts
          • Directory[id]/
            • route.ts
        • Directoryauth/
          • Directorylogin/
            • route.ts
          • Directoryregister/
            • route.ts
          • Directoryrefresh/
            • route.ts
// app/api/middleware.ts
import type { Middleware } from '@cloudwerk/core';
export const middleware: Middleware = async (request, next, context) => {
// CORS headers
const origin = request.headers.get('Origin');
const allowedOrigins = ['https://myapp.com', 'http://localhost:3000'];
const corsHeaders = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
};
if (origin && allowedOrigins.includes(origin)) {
corsHeaders['Access-Control-Allow-Origin'] = origin;
}
// Handle preflight
if (request.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: corsHeaders });
}
// Rate limiting
const ip = request.headers.get('CF-Connecting-IP') ?? 'unknown';
const rateLimitKey = `rate_limit:api:${ip}`;
const requests = parseInt(await context.kv.get(rateLimitKey) ?? '0');
const limit = 100; // requests per minute
if (requests >= limit) {
return new Response(JSON.stringify({ error: 'Rate limit exceeded' }), {
status: 429,
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'Retry-After': '60',
},
});
}
await context.kv.put(rateLimitKey, String(requests + 1), {
expirationTtl: 60,
});
// Execute request
const response = await next(request);
// Add CORS headers to response
Object.entries(corsHeaders).forEach(([key, value]) => {
response.headers.set(key, value);
});
// Add rate limit headers
response.headers.set('X-RateLimit-Limit', String(limit));
response.headers.set('X-RateLimit-Remaining', String(limit - requests - 1));
return response;
};
// app/api/v1/middleware.ts
import type { Middleware } from '@cloudwerk/core';
import { jwtVerify } from 'jose';
// Public routes that don't require authentication
const publicRoutes = [
'/api/v1/auth/login',
'/api/v1/auth/register',
'/api/v1/auth/refresh',
];
export const middleware: Middleware = async (request, next, context) => {
const url = new URL(request.url);
// Skip auth for public routes
if (publicRoutes.some(route => url.pathname.startsWith(route))) {
return next(request);
}
// Get token from Authorization header
const authHeader = request.headers.get('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
return new Response(JSON.stringify({
error: 'Unauthorized',
message: 'Missing or invalid Authorization header',
}), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
const token = authHeader.slice(7);
try {
const secret = new TextEncoder().encode(context.env.JWT_SECRET);
const { payload } = await jwtVerify(token, secret);
// Attach user to context
context.auth.userId = payload.sub as string;
context.auth.user = payload;
} catch (error) {
return new Response(JSON.stringify({
error: 'Unauthorized',
message: 'Invalid or expired token',
}), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
return next(request);
};
// app/api/v1/auth/login/route.ts
import { json } from '@cloudwerk/core';
import { z } from 'zod';
import { SignJWT } from 'jose';
import { verify } from '@cloudwerk/auth';
const LoginSchema = z.object({
email: z.string().email(),
password: z.string().min(1),
});
export async function POST(request: Request, { context }: CloudwerkHandlerContext) {
const body = await request.json();
const result = LoginSchema.safeParse(body);
if (!result.success) {
return json({
error: 'Validation failed',
details: result.error.flatten().fieldErrors,
}, { status: 400 });
}
const { email, password } = result.data;
// Find user
const user = await context.db
.selectFrom('users')
.where('email', '=', email)
.executeTakeFirst();
if (!user) {
return json({ error: 'Invalid credentials' }, { status: 401 });
}
// Verify password
const valid = await verify(password, user.password_hash);
if (!valid) {
return json({ error: 'Invalid credentials' }, { status: 401 });
}
// Generate tokens
const secret = new TextEncoder().encode(context.env.JWT_SECRET);
const accessToken = await new SignJWT({
sub: user.id,
email: user.email,
})
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('15m')
.sign(secret);
const refreshToken = await new SignJWT({
sub: user.id,
type: 'refresh',
})
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(secret);
// Store refresh token
await context.kv.put(`refresh_token:${user.id}`, refreshToken, {
expirationTtl: 7 * 24 * 60 * 60,
});
return json({
accessToken,
refreshToken,
expiresIn: 900,
user: {
id: user.id,
email: user.email,
name: user.name,
},
});
}
// app/api/v1/auth/refresh/route.ts
import { json } from '@cloudwerk/core';
import { jwtVerify, SignJWT } from 'jose';
export async function POST(request: Request, { context }: CloudwerkHandlerContext) {
const { refreshToken } = await request.json();
if (!refreshToken) {
return json({ error: 'Refresh token required' }, { status: 400 });
}
const secret = new TextEncoder().encode(context.env.JWT_SECRET);
try {
const { payload } = await jwtVerify(refreshToken, secret);
if (payload.type !== 'refresh') {
return json({ error: 'Invalid token type' }, { status: 401 });
}
// Verify token is still valid in KV
const storedToken = await context.kv.get(`refresh_token:${payload.sub}`);
if (storedToken !== refreshToken) {
return json({ error: 'Token revoked' }, { status: 401 });
}
// Get user
const user = await context.db
.selectFrom('users')
.where('id', '=', payload.sub as string)
.executeTakeFirst();
if (!user) {
return json({ error: 'User not found' }, { status: 401 });
}
// Generate new access token
const accessToken = await new SignJWT({
sub: user.id,
email: user.email,
})
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('15m')
.sign(secret);
return json({
accessToken,
expiresIn: 900,
});
} catch (error) {
return json({ error: 'Invalid refresh token' }, { status: 401 });
}
}
// app/api/v1/users/route.ts
import { json } from '@cloudwerk/core';
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
role: z.enum(['user', 'admin']).default('user'),
});
const ListQuerySchema = z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(20),
sort: z.enum(['created_at', 'name', 'email']).default('created_at'),
order: z.enum(['asc', 'desc']).default('desc'),
});
export async function GET(request: Request, { context }: CloudwerkHandlerContext) {
const url = new URL(request.url);
const query = Object.fromEntries(url.searchParams);
const params = ListQuerySchema.safeParse(query);
if (!params.success) {
return json({
error: 'Invalid query parameters',
details: params.error.flatten().fieldErrors,
}, { status: 400 });
}
const { page, limit, sort, order } = params.data;
const offset = (page - 1) * limit;
const [users, countResult] = await Promise.all([
context.db
.selectFrom('users')
.select(['id', 'email', 'name', 'role', 'created_at'])
.orderBy(sort, order)
.limit(limit)
.offset(offset)
.execute(),
context.db
.selectFrom('users')
.select(context.db.fn.count('id').as('total'))
.executeTakeFirst(),
]);
const total = countResult?.total ?? 0;
return json({
data: users,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
});
}
export async function POST(request: Request, { context }: CloudwerkHandlerContext) {
const body = await request.json();
const result = CreateUserSchema.safeParse(body);
if (!result.success) {
return json({
error: 'Validation failed',
details: result.error.flatten().fieldErrors,
}, { status: 400 });
}
// Check if email already exists
const existing = await context.db
.selectFrom('users')
.where('email', '=', result.data.email)
.executeTakeFirst();
if (existing) {
return json({ error: 'Email already in use' }, { status: 409 });
}
const user = await context.db
.insertInto('users')
.values({
id: crypto.randomUUID(),
...result.data,
})
.returning(['id', 'email', 'name', 'role', 'created_at'])
.executeTakeFirst();
return json({ data: user }, { status: 201 });
}
// app/api/v1/users/[id]/route.ts
import { json } from '@cloudwerk/core';
import { z } from 'zod';
const UpdateUserSchema = z.object({
name: z.string().min(2).max(100).optional(),
role: z.enum(['user', 'admin']).optional(),
});
export async function GET(request: Request, { params, context }: CloudwerkHandlerContext) {
const user = await context.db
.selectFrom('users')
.select(['id', 'email', 'name', 'role', 'created_at'])
.where('id', '=', params.id)
.executeTakeFirst();
if (!user) {
return json({ error: 'User not found' }, { status: 404 });
}
return json({ data: user });
}
export async function PUT(request: Request, { params, context }: CloudwerkHandlerContext) {
const body = await request.json();
const result = UpdateUserSchema.safeParse(body);
if (!result.success) {
return json({
error: 'Validation failed',
details: result.error.flatten().fieldErrors,
}, { status: 400 });
}
const user = await context.db
.updateTable('users')
.set({
...result.data,
updated_at: new Date().toISOString(),
})
.where('id', '=', params.id)
.returning(['id', 'email', 'name', 'role', 'created_at'])
.executeTakeFirst();
if (!user) {
return json({ error: 'User not found' }, { status: 404 });
}
return json({ data: user });
}
export async function DELETE(request: Request, { params, context }: CloudwerkHandlerContext) {
const deleted = await context.db
.deleteFrom('users')
.where('id', '=', params.id)
.returning(['id'])
.executeTakeFirst();
if (!deleted) {
return json({ error: 'User not found' }, { status: 404 });
}
return new Response(null, { status: 204 });
}
// app/api/error.ts
export function handleError(error: unknown): Response {
console.error('API Error:', error);
if (error instanceof z.ZodError) {
return new Response(JSON.stringify({
error: 'Validation failed',
details: error.flatten().fieldErrors,
}), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
if (error instanceof NotFoundError) {
return new Response(JSON.stringify({
error: 'Not found',
message: error.message,
}), {
status: 404,
headers: { 'Content-Type': 'application/json' },
});
}
// Generic error
return new Response(JSON.stringify({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? error.message : undefined,
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
  • Add API versioning strategy
  • Implement webhook notifications
  • Add request logging and analytics
  • Create OpenAPI documentation
  • Add integration tests