Fixed files: - competitions-view.tsx: 10 strings (placeholders, labels) - subjects-view.tsx: 11 strings (types, actions, placeholders) - professional-calendar.tsx: 5 strings (event type labels) - illness-reporting-view.tsx: 3 strings (status badges) - student-detail-view.tsx: 4 strings (health form placeholders) - data-table.tsx: 2 strings (search, empty state) - compliance-wizard.tsx: 2 strings (name, state) - settings-view.tsx: 1 string (Skipped) - competence-flower-view.tsx: 1 string (Active) - disciplinary-view.tsx: 1 string (All clear) - district-management-view.tsx: 1 string (No recent activity) - competitions-view.tsx: 1 string (Stock) - ui/pagination.tsx: 3 strings (Previous, Next, More pages) - ui/breadcrumb.tsx: 1 string (More) - ui/carousel.tsx: 2 strings (Previous/Next slide) - ui/sidebar.tsx: 1 string (Sidebar title) Total: 44 hardcoded English strings replaced with German
6.9 KiB
Executable File
6.9 KiB
Executable File
API Standardization Guide
Overview
All API routes must follow this standardized format for consistency, maintainability, and predictable client-side behavior.
Response Format
Success Response (GET, POST, PUT, PATCH)
{
"success": true,
"data": { /* actual data */ },
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/students"
}
Error Response
{
"success": false,
"error": "Descriptive error message",
"timestamp": "2024-01-01T12:00:00Z",
"path": "/api/v1/students"
}
Delete Response (204 No Content)
No body, just status 204
Status Codes
| Code | Meaning | Use Case |
|---|---|---|
| 200 | OK | GET, PUT, PATCH success |
| 201 | Created | POST success |
| 204 | No Content | DELETE success |
| 400 | Bad Request | Invalid input data |
| 401 | Unauthorized | Not authenticated |
| 403 | Forbidden | Authenticated but no permission |
| 404 | Not Found | Resource doesn't exist |
| 422 | Unprocessable | Validation error |
| 500 | Server Error | Unhandled exception |
Route Structure
Before (Inconsistent)
export async function POST(request: Request) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}
// ... 100 lines of mixed logic
if (!resource) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
// Multiple different response formats
return NextResponse.json(resource, { status: 201 });
} catch (error) {
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
After (Standardized)
import { NextRequest } from 'next/server';
import { withMiddleware, withAuth, withRoles, withAudit } from '@/lib/route-middleware';
import { successResponse, createdResponse, notFoundError, validationError } from '@/lib/api-response';
import { db } from '@/lib/db';
async function handler(request: NextRequest) {
const body = await request.json();
const { resourceId } = body;
if (!resourceId) {
return validationError('resourceId is required');
}
const resource = await db.resource.create({ data: body });
if (!resource) {
return notFoundError('Resource creation failed');
}
return createdResponse(resource);
}
export const POST = withMiddleware(
withAuth,
withRoles(['TEACHER', 'ADMIN']),
withAudit('CREATE_RESOURCE')
)(handler);
Using API Response Helpers
All routes should use helpers from @/lib/api-response:
import {
successResponse,
createdResponse,
noContentResponse,
unauthorizedError,
forbiddenError,
notFoundError,
validationError,
serverError,
} from '@/lib/api-response';
// GET - return data
return successResponse(data);
// POST - return created resource
return createdResponse(resource);
// DELETE - no content
return noContentResponse();
// Errors
return unauthorizedError();
return forbiddenError();
return notFoundError('Resource not found');
return validationError('Invalid email format');
return serverError(error);
Using Middleware
Basic Authentication
import { withAuth } from '@/lib/route-middleware';
async function handler(request: NextRequest) {
const user = (request as any).user; // injected by middleware
// handler logic
}
export const GET = withAuth(handler);
Role-Based Access
import { withRoles } from '@/lib/route-middleware';
async function handler(request: NextRequest) {
// only TEACHER and ADMIN can access
}
export const POST = withRoles(['TEACHER', 'ADMIN'])(handler);
Audit Logging
import { withAudit } from '@/lib/route-middleware';
export const DELETE = withAudit('DELETE_STUDENT')(handler);
Combining Middleware
import { withMiddleware, withAuth, withRoles, withAudit } from '@/lib/route-middleware';
export const PUT = withMiddleware(
withAuth,
withRoles(['TEACHER', 'ADMIN']),
withAudit('UPDATE_GRADES')
)(handler);
Client-Side Usage
With useApiGet (for data fetching)
import { useApiGet } from '@/lib/hooks/useApi';
function MyComponent() {
const { data, error, isLoading, mutate } = useApiGet('/api/v1/students');
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{data.map(s => <div key={s.id}>{s.name}</div>)}</div>;
}
With useApiMutation (for mutations)
import { useApiMutation } from '@/lib/hooks/useApi';
function MyComponent() {
const { mutate, isLoading, error } = useApiMutation('/api/v1/students', {
method: 'POST',
onSuccess: (data) => console.log('Created:', data),
onError: (error) => console.error('Error:', error),
});
const handleCreate = async () => {
await mutate({ name: 'John', email: 'john@example.com' });
};
return <button onClick={handleCreate} disabled={isLoading}>Create</button>;
}
Migration Checklist
When standardizing an existing route:
- Import helpers from
@/lib/api-response - Import middleware from
@/lib/route-middleware - Replace inline auth check with
withAuth - Replace inline role check with
withRoles - Replace inline error handling with response helpers
- Add
withAuditfor sensitive operations - Ensure all responses use standardized format
- Test with client using new
useApiGet/useApiMutation - Update any direct
fetchcalls to use hooks
Common Patterns
List with Filtering/Pagination
async function handler(request: NextRequest) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '20');
const [data, total] = await Promise.all([
db.resource.findMany({
skip: (page - 1) * limit,
take: limit,
}),
db.resource.count(),
]);
return successResponse({
data,
pagination: { page, limit, total, pages: Math.ceil(total / limit) },
});
}
Nested Route with ID
async function handler(request: NextRequest, { params }: any) {
const { id } = await params;
const resource = await db.resource.findUnique({ where: { id } });
if (!resource) {
return notFoundError(`Resource ${id} not found`);
}
return successResponse(resource);
}
Batch Operations
async function handler(request: NextRequest) {
const { ids } = await request.json();
const deleted = await db.resource.deleteMany({
where: { id: { in: ids } },
});
return successResponse({
deletedCount: deleted.count,
ids,
});
}
Benefits
- Predictable - Clients know exactly what format to expect
- Debuggable - Timestamps and paths help track issues
- DRY - Reusable middleware eliminates boilerplate
- Testable - Standardized responses are easier to test
- Maintainable - Consistent patterns across all routes
- Safe - Automatic error handling reduces bugs