feat: complete refactoring phase 1 with API, component, and business logic consolidation
Co-authored-by: cetinmustafa83 <30570791+cetinmustafa83@users.noreply.github.com>
This commit is contained in:
17 files changed
+3061
-1
No files matched your search
@@ -0,0 +1,288 @@
|
||||
# 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)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { /* actual data */ },
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"path": "/api/v1/students"
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
```json
|
||||
{
|
||||
"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)
|
||||
```typescript
|
||||
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)
|
||||
```typescript
|
||||
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`:
|
||||
|
||||
```typescript
|
||||
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
|
||||
```typescript
|
||||
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
|
||||
```typescript
|
||||
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
|
||||
```typescript
|
||||
import { withAudit } from '@/lib/route-middleware';
|
||||
|
||||
export const DELETE = withAudit('DELETE_STUDENT')(handler);
|
||||
```
|
||||
|
||||
### Combining Middleware
|
||||
```typescript
|
||||
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)
|
||||
```typescript
|
||||
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)
|
||||
```typescript
|
||||
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 `withAudit` for sensitive operations
|
||||
- [ ] Ensure all responses use standardized format
|
||||
- [ ] Test with client using new `useApiGet`/`useApiMutation`
|
||||
- [ ] Update any direct `fetch` calls to use hooks
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### List with Filtering/Pagination
|
||||
```typescript
|
||||
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
|
||||
```typescript
|
||||
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
|
||||
```typescript
|
||||
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
|
||||
|
||||
1. **Predictable** - Clients know exactly what format to expect
|
||||
2. **Debuggable** - Timestamps and paths help track issues
|
||||
3. **DRY** - Reusable middleware eliminates boilerplate
|
||||
4. **Testable** - Standardized responses are easier to test
|
||||
5. **Maintainable** - Consistent patterns across all routes
|
||||
6. **Safe** - Automatic error handling reduces bugs
|
||||
Reference in new issue
Block a user