Build AI-powered Express.js APIs with TypeScript, OpenAI integration, Zod validation, and structured routing patterns. Includes image processing, SEO tools, and hot reload development setup.
Develop Express.js API servers with TypeScript, focusing on AI-powered content generation services, structured validation, and clean architecture patterns. This skill guides you through building production-ready REST APIs with OpenAI integration, file uploads, and comprehensive error handling.
Organize the codebase with clear separation of concerns:
```
src/
├── app.ts # Express server entry point
├── routes/ # API route handlers (versioned)
│ └── v1/ # Version 1 endpoints
│ ├── image/ # Image-related routes
│ └── seo/ # SEO-related routes
├── models/ # Zod validation schemas
│ ├── image/
│ └── seo/
├── validation.ts # Validation middleware
└── middleware/ # Custom middleware (error handling, etc.)
dist/ # Compiled JavaScript output
```
Create a `.env` file in the project root:
```env
OPENAI_API_KEY=sk-...
PORT=3000
```
Use strict mode with ES modules and CommonJS compilation target. Enable module resolution bundler mode for modern dependencies.
Set up Express with essential middleware:
```typescript
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Versioned routing
import v1Routes from './routes/v1';
app.use('/v1', v1Routes);
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal server error' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
```
Create type-safe schemas in `src/models/`:
```typescript
import { z } from 'zod';
// Body validation schema
export const SeoImproveTextSchema = z.object({
text: z.string().min(1),
language: z.string().optional(),
instructions: z.string().optional()
});
// Parameter validation schema
export const UserParamsSchema = z.object({
userId: z.string().uuid()
});
// Query validation schema
export const SearchQuerySchema = z.object({
q: z.string().min(1),
limit: z.coerce.number().min(1).max(100).default(10),
offset: z.coerce.number().min(0).default(0)
});
// Export inferred types
export type SeoImproveTextInput = z.infer<typeof SeoImproveTextSchema>;
```
Create reusable validation middleware in `src/validation.ts`:
```typescript
import { Request, Response, NextFunction } from 'express';
import { ZodSchema } from 'zod';
export function validate(schemas: {
body?: ZodSchema;
params?: ZodSchema;
query?: ZodSchema;
file?: {
fieldName: string;
maxSize: number;
allowedMimeTypes: string[];
};
}) {
return (req: Request, res: Response, next: NextFunction) => {
try {
// Body validation
if (schemas.body) {
req.body = schemas.body.parse(req.body);
}
// Parameter validation
if (schemas.params) {
(req as any).validatedParams = schemas.params.parse(req.params);
}
// Query validation
if (schemas.query) {
(req as any).validatedQuery = schemas.query.parse(req.query);
}
// File validation
if (schemas.file && req.file) {
const { maxSize, allowedMimeTypes, fieldName } = schemas.file;
if (req.file.fieldname !== fieldName) {
return res.status(400).json({ error: `Expected field: ${fieldName}` });
}
if (req.file.size > maxSize) {
return res.status(400).json({
error: `File too large. Max size: ${maxSize / 1024 / 1024}MB`
});
}
if (!allowedMimeTypes.includes(req.file.mimetype)) {
return res.status(400).json({
error: `Invalid file type. Allowed: ${allowedMimeTypes.join(', ')}`
});
}
}
next();
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
error: 'Validation failed',
details: error.errors
});
}
next(error);
}
};
}
```
Apply validation to route handlers:
```typescript
import { Router } from 'express';
import { validate } from '../../validation';
import { SeoImproveTextSchema } from '../../models/seo';
const router = Router();
router.post('/improve-text', validate({
body: SeoImproveTextSchema
}), async (req, res) => {
// req.body is now validated and typed
const { text, language, instructions } = req.body;
// Your logic here
res.json({ result: 'improved text' });
});
export default router;
```
Install dependencies:
```bash
npm install ai @ai-sdk/openai
```
```typescript
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
async function generateAltText(imageUrl: string, customInstructions?: string) {
const { text } = await generateText({
model: openai('gpt-4-turbo'),
messages: [
{
role: 'user',
content: [
{ type: 'text', text: customInstructions || 'Generate alt text for this image' },
{ type: 'image', image: imageUrl }
]
}
]
});
return text;
}
```
```typescript
import multer from 'multer';
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 } // 10MB
});
router.post('/alt-from-image',
upload.single('image'),
validate({
file: {
fieldName: 'image',
maxSize: 10 * 1024 * 1024,
allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
}
}),
async (req, res) => {
const imageBuffer = req.file!.buffer;
const base64Image = imageBuffer.toString('base64');
// Process image with AI
}
);
```
```typescript
// src/routes/v1/index.ts
import { Router } from 'express';
import imageRoutes from './image';
import seoRoutes from './seo';
const router = Router();
router.use('/image', imageRoutes);
router.use('/seo', seoRoutes);
export default router;
```
**Endpoint**: `POST /v1/image/alt-from-url`
```json
{
"imageUrl": "https://example.com/image.jpg",
"language": "en",
"customInstructions": "Focus on accessibility"
}
```
**Endpoint**: `POST /v1/image/alt-from-image`
Multipart form data with `image` field.
**Endpoint**: `POST /v1/seo/improve-text`
```json
{
"text": "Original text to improve",
"language": "en",
"instructions": "Optimize for readability and SEO"
}
```
Follow these ESLint rules:
1. Use structured error responses with appropriate HTTP status codes
2. Log errors server-side, return sanitized messages to clients
3. Wrap async route handlers to catch promise rejections
4. Validate all inputs before processing
5. Handle file upload errors separately from validation errors
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/express-typescript-api-server/raw