rate limiting with @fastify/rate-limit
node 20.13.0
pnpm
@fastify/rate-limit ^9.1.0
fastify ^4.27.0
@trpc/server 11.0.0-rc.401
gotta break this up because discord says its too long but let me know if you find this helpful!
// server/index.ts
import fastify from 'fastify';
import fastifyRateLimit from '@fastify/rate-limit';
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
import { appRouter } from './routers/index.js';
import { createContext } from './constants/context.js';
const server = fastify({ logger: { transport: { target: 'pino-pretty' } } });
// ...
await server.register(
fastifyRateLimit,
{ global: false },
);
await server.register(
fastifyTRPCPlugin,
{
prefix: '/trpc',
trpcOptions: {
createContext: createContext.bind(server),
router: appRouter,
},
},
);
// ...
// server/index.ts
import fastify from 'fastify';
import fastifyRateLimit from '@fastify/rate-limit';
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
import { appRouter } from './routers/index.js';
import { createContext } from './constants/context.js';
const server = fastify({ logger: { transport: { target: 'pino-pretty' } } });
// ...
await server.register(
fastifyRateLimit,
{ global: false },
);
await server.register(
fastifyTRPCPlugin,
{
prefix: '/trpc',
trpcOptions: {
createContext: createContext.bind(server),
router: appRouter,
},
},
);
// ...
// server/routers/index.ts
import { inferRouterOutputs } from '@trpc/server';
import {
middleware,
procedure,
router,
} from '../constants/trpc.js';
import rateLimited from '../middleware/rate-limited.js';
export const appRouter = router({
demo: procedure
.use(rateLimited({ max: 1 }))
.query(() => null)
});
export type AppRouter = typeof appRouter;
export type RouterOutputs = inferRouterOutputs<AppRouter>;
// server/routers/index.ts
import { inferRouterOutputs } from '@trpc/server';
import {
middleware,
procedure,
router,
} from '../constants/trpc.js';
import rateLimited from '../middleware/rate-limited.js';
export const appRouter = router({
demo: procedure
.use(rateLimited({ max: 1 }))
.query(() => null)
});
export type AppRouter = typeof appRouter;
export type RouterOutputs = inferRouterOutputs<AppRouter>;
// server/constants/trpc.ts
import { initTRPC } from '@trpc/server';
import type { Context } from './context';
const {
middleware,
procedure,
router,
} = initTRPC.context<Context>().create();
export {
middleware,
procedure,
router,
};
// server/constants/trpc.ts
import { initTRPC } from '@trpc/server';
import type { Context } from './context';
const {
middleware,
procedure,
router,
} = initTRPC.context<Context>().create();
export {
middleware,
procedure,
router,
};
// server/constants/context.ts
import { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
import { FastifyInstance } from 'fastify';
export function createContext(
this: FastifyInstance,
{
req,
res,
}: CreateFastifyContextOptions,
) {
return {
app: this,
req,
res,
};
};
export type Context = ReturnType<typeof createContext>;
// server/constants/context.ts
import { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
import { FastifyInstance } from 'fastify';
export function createContext(
this: FastifyInstance,
{
req,
res,
}: CreateFastifyContextOptions,
) {
return {
app: this,
req,
res,
};
};
export type Context = ReturnType<typeof createContext>;
1 Reply
// server/middleware/rate-limited.ts
import { TRPCError } from '@trpc/server';
import type { RateLimitOptions } from '@fastify/rate-limit';
import { middleware } from '../constants/trpc.js';
const rateLimited = (options: RateLimitOptions) => middleware(
async ({
ctx,
next,
}) => {
try {
await new Promise(
(
resolve,
reject,
) => {
ctx
.app
.rateLimit(options)
.bind(ctx.app)(ctx.req, ctx.res)
.then(resolve)
.catch((error) => {
reject(new TRPCError({
code: 'TOO_MANY_REQUESTS',
message: (error as Error).message,
}));
});
},
);
return next();
} catch (error) {
ctx.app.log.error(error);
if (error instanceof TRPCError) throw error;
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: (error as Error).message,
});
}
},
);
export default rateLimited;
// server/middleware/rate-limited.ts
import { TRPCError } from '@trpc/server';
import type { RateLimitOptions } from '@fastify/rate-limit';
import { middleware } from '../constants/trpc.js';
const rateLimited = (options: RateLimitOptions) => middleware(
async ({
ctx,
next,
}) => {
try {
await new Promise(
(
resolve,
reject,
) => {
ctx
.app
.rateLimit(options)
.bind(ctx.app)(ctx.req, ctx.res)
.then(resolve)
.catch((error) => {
reject(new TRPCError({
code: 'TOO_MANY_REQUESTS',
message: (error as Error).message,
}));
});
},
);
return next();
} catch (error) {
ctx.app.log.error(error);
if (error instanceof TRPCError) throw error;
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: (error as Error).message,
});
}
},
);
export default rateLimited;