| title |
|---|
Middlewares |
Ship provides two essential middleware utilities for API routes:
- Rate Limit Middleware — protects your API endpoints from excessive requests
- Validate Middleware — validates incoming request data using Zod schemas
Both middlewares are located in /api/src/middlewares and can be imported and applied to any route.
The rate limit middleware protects your API endpoints from abuse by limiting the number of requests a user can make within a specified time window. It automatically uses Redis when available, falling back to in-memory storage for development environments.
The rateLimitMiddleware function accepts an options object with the following parameters:
limitDuration(optional) — Time window in seconds. Default:60secondsrequestsPerDuration(optional) — Maximum number of requests allowed within the time window. Default:10errorMessage(optional) — Custom error message shown when rate limit is exceeded. Default:'Looks like you are moving too fast. Retry again in few minutes.'
- Automatic Storage Selection: Uses Redis if
REDIS_URIis configured, otherwise falls back to in-memory storage - User-Specific Limits: Rate limits are applied per authenticated user (based on
user._id) or per IP address for unauthenticated requests - Response Headers: Includes rate limit headers in the response for client-side tracking
import createEndpoint from 'routes/createEndpoint';
import { rateLimitMiddleware } from 'middlewares';
export default createEndpoint({
method: 'post',
path: '/send-email',
schema,
middlewares: [
rateLimitMiddleware({
limitDuration: 300, // 5 minutes
requestsPerDuration: 5, // 5 requests per 5 minutes
errorMessage: 'Too many emails sent. Please try again later.',
}),
],
async handler(ctx) {
// Your handler logic here
return { success: true };
},
});- Protecting email sending endpoints
- Rate limiting authentication attempts
- Preventing API abuse on expensive operations
- Throttling public API endpoints
The validate middleware automatically validates incoming request data against a Zod schema. It combines data from request body, files, query parameters, and route parameters into a single validated object.
The middleware validates the following request data:
- Request body (
ctx.request.body) - Uploaded files (
ctx.request.files) - Query parameters (
ctx.query) - Route parameters (
ctx.params)
If validation fails, it automatically throws a 400 error with detailed field-level error messages. If validation succeeds, the validated data is available via ctx.validatedData.
import { z } from 'zod';
import createEndpoint from 'routes/createEndpoint';
import { AppKoaContext } from 'types';
// Define your schema
const schema = z.object({
email: z.email('Email format is incorrect'),
firstName: z.string().min(1, 'First name is required').max(100),
lastName: z.string().min(1, 'Last name is required').max(100),
age: z.number().int().positive().optional(),
});
type CreateUserData = z.infer<typeof schema>;
async function handler(ctx: AppKoaContext<CreateUserData>) {
// Access validated data
const { email, firstName, lastName, age } = ctx.validatedData;
// Your handler logic here
ctx.body = { email, firstName, lastName, age };
}
export default createEndpoint({
method: 'post',
path: '/users',
schema,
handler,
});When validation fails, the middleware returns a structured error response with field-specific error messages:
{
"clientErrors": {
"email": ["Email format is incorrect"],
"firstName": ["First name is required"]
}
}