API Boilerplate
REST API boilerplate with auth, validation, and tests.
Structure
api/
├── src/
│ ├── routes/
│ │ ├── leads.ts
│ │ ├── properties.ts
│ │ └── offers.ts
│ ├── middleware/
│ │ ├── auth.ts
│ │ ├── validate.ts
│ │ └── rateLimit.ts
│ ├── lib/
│ │ ├── proptech.ts
│ │ └── db.ts
│ └── index.ts
├── tests/
│ └── leads.test.ts
└── package.json
Auth Middleware
// src/middleware/auth.ts
import { NextRequest, NextResponse } from 'next/server';
export async function authMiddleware(request: NextRequest) {
const apiKey = request.headers.get('x-api-key');
if (!apiKey) {
return NextResponse.json(
{ error: 'API key required' },
{ status: 401 }
);
}
// Validate API key
const isValid = await validateApiKey(apiKey);
if (!isValid) {
return NextResponse.json(
{ error: 'Invalid API key' },
{ status: 401 }
);
}
return NextResponse.next();
}
Validation Middleware
// src/middleware/validate.ts
import { z } from 'zod';
import { NextRequest, NextResponse } from 'next/server';
export function validate<T>(schema: z.Schema<T>) {
return async (request: NextRequest) => {
try {
const body = await request.json();
const data = schema.parse(body);
return { data };
} catch (error) {
if (error instanceof z.ZodError) {
return {
error: NextResponse.json(
{ error: 'Validation failed', details: error.errors },
{ status: 400 }
),
};
}
throw error;
}
};
}
Test Example
// tests/leads.test.ts
import { describe, it, expect } from 'vitest';
describe('Leads API', () => {
it('creates a lead', async () => {
const res = await fetch('/api/leads', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'test_key',
},
body: JSON.stringify({
name: 'Test User',
email: 'test@example.com',
phone: '555-1234',
}),
});
expect(res.status).toBe(201);
const data = await res.json();
expect(data.id).toBeDefined();
});
it('validates required fields', async () => {
const res = await fetch('/api/leads', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'test_key',
},
body: JSON.stringify({}),
});
expect(res.status).toBe(400);
});
});