Skip to content

Migrate Backend to Strict TypeScript #137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
1 change: 1 addition & 0 deletions .tsbuildinfo

Large diffs are not rendered by default.

260 changes: 260 additions & 0 deletions API_PATTERNS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
# Type-Safe API Route Handlers

This implementation provides comprehensive type-safe Express.js patterns with proper middleware typing and input validation schemas.

## Overview

The implementation consists of several key components:

- **api-handlers.ts**: Core type-safe route handler interfaces
- **middleware.ts**: Typed middleware patterns with proper error handling
- **validation.ts**: Input validation schemas with TypeScript types
- **error-utils.ts**: Utility functions for creating properly typed RPC errors
- **example-routes.ts**: Complete example implementations
- **app.ts**: Application setup and integration example

## Key Features

### 1. Type-Safe Route Handlers

```typescript
import { typedHandler, TypedRequest } from './api-handlers';

// Define your types
interface CreateUserRequest {
email: string;
name: string;
age?: number;
}

interface User {
id: string;
email: string;
name: string;
age?: number;
createdAt: string;
}

// Type-safe handler with automatic error handling
const createUser = typedHandler<CreateUserRequest, never, never, User>(
async (req: TypedRequest<CreateUserRequest, never, never>) => {
const user = await userService.createUser(req.body);
return {
success: true,
data: user,
timestamp: new Date().toISOString()
};
}
);
```

### 2. Input Validation Schemas

```typescript
import { Schema, Validators, validateRequest } from './validation';

// Define validation schema
const CreateUserSchema = new Schema<CreateUserRequest>({
email: Validators.email({ required: true }),
name: Validators.string({ required: true, minLength: 2, maxLength: 100 }),
age: Validators.positiveInteger({ min: 13, max: 120 })
});

// Use in routes
router.post('/users',
...validateRequest({ body: CreateUserSchema }),
createUser
);
```

### 3. Typed Middleware

```typescript
import { createAuthMiddleware, createAuthzMiddleware } from './middleware';

// Authentication middleware
const authMiddleware = createAuthMiddleware(async (token: string) => {
// Validate token and return user context
return await validateJWT(token);
});

// Authorization middleware
const authzMiddleware = createAuthzMiddleware(
async (user: UserContext, resource: string, action: string) => {
return user.permissions.includes(`${action}:${resource}`);
}
);

// Use in routes
router.post('/users',
authMiddleware,
authzMiddleware('users', 'write'),
...validateRequest({ body: CreateUserSchema }),
createUser
);
```

## Complete Route Example

```typescript
import { Router } from 'express';
import { typedHandler, TypedRequest } from './api-handlers';
import { validateRequest, Schema, Validators } from './validation';

const router = Router();

// Schema definition
const CreateUserSchema = new Schema<{
email: string;
name: string;
age?: number;
}>({
email: Validators.email({ required: true }),
name: Validators.string({ required: true, minLength: 2 }),
age: Validators.positiveInteger({ min: 13, max: 120 })
});

// Type-safe route handler
router.post('/users',
...validateRequest({ body: CreateUserSchema }),
typedHandler<CreateUserRequest, never, never, User>(
async (req: TypedRequest<CreateUserRequest, never, never>) => {
// req.body is fully typed and validated
const user = await userService.createUser(req.body);

return {
success: true,
data: user, // TypeScript ensures this matches User interface
timestamp: new Date().toISOString()
};
}
)
);
```

## Available Validators

The validation system includes pre-built validators:

```typescript
import { Validators } from './validation';

// String validators
Validators.string({ required: true, minLength: 2, maxLength: 100 })
Validators.email({ required: true })
Validators.uuid({ required: true })

// Number validators
Validators.number({ min: 0, max: 100 })
Validators.positiveInteger({ min: 1 })

// Boolean validator
Validators.boolean({ required: true })

// Array validator
Validators.array(Validators.string(), { minLength: 1, maxLength: 10 })

// Custom validation
Validators.string({
custom: (value: string) => {
return value.includes('forbidden') ? 'Value contains forbidden content' : null;
}
})
```

## Error Handling

All errors are automatically formatted using the RPC interface pattern with proper typing:

```typescript
import { ErrorUtils } from './error-utils';

// Create validation error
const validationError = ErrorUtils.validation({
email: ["email format is invalid"],
name: ["name is required"]
});

// Create authentication error
const authError = ErrorUtils.authentication('Invalid token');

// Create not found error
const notFoundError = ErrorUtils.notFound('user', 'user123');

// Error responses are automatically formatted:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"fieldErrors": {
"email": ["email format is invalid"],
"name": ["name is required"]
},
"timestamp": "2024-01-01T00:00:00.000Z"
}
}
```

## Middleware Chain Example

```typescript
import {
requestContextMiddleware,
loggingMiddleware,
createAuthMiddleware,
createRateLimitMiddleware
} from './middleware';

const router = Router();

// Global middleware
router.use(requestContextMiddleware); // Adds request ID and context
router.use(loggingMiddleware); // Logs requests/responses
router.use(rateLimitMiddleware()); // Rate limiting

// Protected routes
router.use('/admin/*', authMiddleware);
router.use('/admin/*', authzMiddleware('admin', 'access'));

// Route-specific validation
router.post('/users',
...validateRequest({ body: CreateUserSchema }),
createUser
);
```

## Benefits

1. **Full Type Safety**: TypeScript ensures request/response types are correct at compile time
2. **Automatic Validation**: Input validation with clear error messages
3. **Consistent Error Handling**: All errors follow the same RPC format
4. **Middleware Composition**: Easily compose and reuse middleware
5. **Developer Experience**: Excellent IDE support with autocomplete and type checking
6. **Runtime Safety**: Validation prevents runtime errors from malformed input
7. **Maintainability**: Clear separation of concerns and reusable patterns

## Usage in Applications

1. **Install Dependencies**:
```bash
npm install express @types/express
```

2. **Import and Use**:
```typescript
import { createApp } from './app';

const app = createApp();
app.listen(3000, () => {
console.log('Server running on port 3000');
});
```

3. **Define Your Schemas and Handlers**:
- Create validation schemas using the `Schema` class
- Use `typedHandler` for type-safe route handlers
- Apply middleware using the provided factories
- Follow the RPC interface pattern for consistent responses

This pattern ensures your Express.js APIs are fully type-safe, well-validated, and maintainable while providing excellent developer experience.
Loading