Custom Strapi Middleware
Is your Strapi project struggling with 1000 requests per minute? Logs scattered, clients complaining about 429 errors? Typical scenario: navigation starts lagging, response time drops to 800 ms. To fix this, we implement custom middleware — functions that intercept requests before and after controller processing. Our experience: we've implemented such middleware for over 50 projects — from e-commerce stores to complex CMS. Custom Strapi middleware allows flexible request management: throttling, audit trail, access control, response modification.
Middleware is a concept familiar to Express or Koa developers: they work in a chain, passing control via await next(). Strapi supports two types: global (applied to all routes) and route-specific (only to certain routes). Here's a comparison:
| Type | Scope | Registration |
|---|---|---|
| Global | All requests | config/middlewares.js |
| Route | Specific route | Route configuration |
How rate limiting affects Strapi performance
In-memory rate limiting is one of the most requested middleware. It stores request timestamps per IP in a Map. When the number of requests in a window (e.g., 100 per 60 seconds) exceeds the limit, it returns 429. In-memory is 10x faster than file-based — minimal latency under 1 ms. For distributed systems, we use Redis (adds 1-3 ms latency but scales horizontally); we help with that too. Here is typical code:
// src/middlewares/rate-limit.ts const requests = new Map<string, number[]>() export default (config: any) => { const { maxRequests = 100, windowMs = 60_000 } = config return async (ctx: any, next: any) => { const ip = ctx.request.ip const now = Date.now() const windowStart = now - windowMs const timestamps = (requests.get(ip) || []).filter(t => t > windowStart) if (timestamps.length >= maxRequests) { ctx.status = 429 ctx.body = { error: 'Too Many Requests' } ctx.set('Retry-After', String(Math.ceil(windowMs / 1000))) return } timestamps.push(now) requests.set(ip, timestamps) await next() } } Configure in config/middlewares.js as 'global::rate-limit' with maxRequests and windowMs parameters.
In-memory rate limiting with Map yields under 1 ms latency, Redis gives 1-3 ms but scales horizontally. For projects up to 1000 requests/sec, in-memory suffices; from 1000 to 5000, we use Redis. On one project (e-commerce store with 3000 products), after implementing rate limiting and caching middleware, TTFB dropped from 800 ms to 200 ms (a 75% reduction), and 429 errors completely disappeared. This saved $500 per month on server resources (≈$6000 annually).
Why log requests via middleware
The default Strapi logger cannot track slow requests. Custom middleware with timing solves this: it logs all requests and marks slow ones (over 1 second). This helps find bottlenecks before they become critical. Example:
// src/middlewares/request-logger.ts export default (config: any, { strapi }: any) => { return async (ctx: any, next: any) => { const start = Date.now() await next() const duration = Date.now() - start const { method, url, status } = ctx if (duration > 1000) { strapi.log.warn(`Slow request: ${method} ${url} — ${duration}ms (${status})`) } strapi.log.debug(`${method} ${url} — ${duration}ms [${status}]`) } } It is registered globally via the middleware configuration file. We guarantee you won't miss any problematic requests after setup.
How we implement subscription check via middleware
For premium content, we use route middleware that checks the user's active subscription. Such middleware only applies to a single route, not burdening others:
// src/middlewares/check-subscription.ts export default (config: any, { strapi }: any) => { return async (ctx: any, next: any) => { const userId = ctx.state.user?.id if (!userId) { ctx.unauthorized('Authentication required') return } const user = await strapi.entityService.findOne( 'plugin::users-permissions.user', userId, { populate: ['subscription'] } ) if (!user?.subscription?.active) { ctx.forbidden('Active subscription required') return } await next() } } In the route configuration, add the field middlewares: ['api::check-subscription'].
Why middleware matter for Core Web Vitals
Each middleware can improve Core Web Vitals. For example, caching middleware reduces TTFB, and rate limiting prevents server overload and INP degradation. If your API returns data in 800 ms, it directly affects LCP. Middleware can cache responses or limit frequency, reducing latency by up to 75%.
Response transformation and multilingual support
Often you need to add a computed field — for example, discount percentage. Middleware intercepts the response and adds discountPercent. For multilingual projects, middleware can automatically determine the locale from the Accept-Language header:
// src/middlewares/add-computed-fields.ts export default () => { return async (ctx: any, next: any) => { await next() if (ctx.url.startsWith('/api/products') && ctx.body?.data) { const transform = (item: any) => ({ ...item, attributes: { ...item.attributes, discountPercent: item.attributes.originalPrice ? Math.round((1 - item.attributes.price / item.attributes.originalPrice) * 100) : 0, }, }) if (Array.isArray(ctx.body.data)) { ctx.body.data = ctx.body.data.map(transform) } else { ctx.body.data = transform(ctx.body.data) } } } } // src/middlewares/locale-redirect.ts const localeMap: Record<string, string> = { 'ru-RU': 'ru', 'en-US': 'en', 'uk-UA': 'uk', } export default () => { return async (ctx: any, next: any) => { if (!ctx.query.locale) { const acceptLang = ctx.get('Accept-Language')?.split(',')[0] || 'ru' const locale = localeMap[acceptLang] || acceptLang.split('-')[0] || 'ru' ctx.query.locale = locale } await next() } } Which middleware are essential for production
The essential set includes throttling, audit trail, access control, and response modification. Additionally, caching, redirects, and CORS can be added. We help select the optimal set for your load. The cost varies depending on complexity; a typical set of 3–4 middleware costs $500–$1000 for development. We provide a detailed quote after analyzing your project.
Step-by-step rate limiting middleware configuration
- Create the file
src/middlewares/rate-limit.tswith the code from the example. - In
config/middlewares.js, add'global::rate-limit'to themiddlewaresarray. - Configure
maxRequestsandwindowMsparameters according to your load (e.g., 100 requests per 60 seconds). - Test locally with
npx strapi develop. - For production, use Redis via external storage if scaling beyond 1000 requests/sec.
For more details on middleware configuration, see the official Strapi documentation.
Work process and what's included
We work as follows: analyze requirements → design middleware architecture → write code with tests → deploy to staging → deploy to production. The result includes:
- Source code of middleware with comments
- Installation and configuration documentation
- Git repository access
- 1-hour online training for your team
- 2 weeks of support after launch
Approximate timeline
Development of a set of 3-4 middleware (e.g., throttling, audit trail, access control, response modification) takes 1 to 2 days. Cost is calculated individually, typically $500–$1000.
Contact us to evaluate your project — we'll select the optimal middleware set for your load. Get a consultation on custom Strapi middleware and order turnkey development with a 2-week support guarantee.







