I'm working on a Fastify application with TypeScript and I've added authentication using @fastify/jwt and @fastify/cookie. I created a custom decorator named authenticate, which I'm using in my route files. However, I'm encountering a TypeScript error when I try to organize my code by moving the decorator type declaration to a separate file.
When I define the authenticate decorator directly in my server.ts, everything works fine. But if I try to move the type declaration to a separate file (e.g., fastify.d.ts in the root of my project and include it in tsconfig.json), TypeScript starts showing an error.
{
"resource": "/u:/dekada/api/src/models/teacher/teacher.route.ts",
"owner": "typescript",
"code": "2339",
"severity": 8,
"message": "Property 'authenticate' does not exist on type 'FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse<IncomingMessage>, FastifyBaseLogger, FastifyTypeProviderDefault>'.",
"source": "ts",
"startLineNumber": 32,
"startColumn": 41,
"endLineNumber": 32,
"endColumn": 53
}
I've defined my custom decorator in fastify.d.ts like this:
import { FastifyInstance } from "fastify";
declare module "fastify" {
export interface FastifyInstance {
authenticate: any; // Replace 'any' with a more specific type if possible
}
}
And my 'tsconfig.json includes:
{
"include": [
"src/**/*",
"fastify.d.ts"
],
"exclude": ["node_modules"]
}
Am I doing something wrong with the authenticate decorator, or should I specify more information in the module declaration? Any insights or suggestions on how to resolve this issue would be greatly appreciated.
This is my server.ts:
import fastify, { FastifyReply, FastifyRequest } from "fastify";
import dotenv from "dotenv";
import { logger } from "./common/utils";
import { jwtPlugin, cookiePlugin } from "./common/plugins";
import { customErrorHandler } from "./common/errors/errorHandler";
declare module "fastify" {
export interface FastifyInstance {
authenticate: any;
}
}
import teacherRoutes from "./models/teacher/teacher.route";
dotenv.config();
const server = fastify({ logger, disableRequestLogging: true });
server.setErrorHandler(customErrorHandler);
// Register plugins
server.register(jwtPlugin);
server.register(cookiePlugin);
//
server.decorate(
"authenticate",
async (request: FastifyRequest, reply: FastifyReply) => {
const token = request.cookies.access_token;
if (!token) {
return reply.status(401).send({ message: "Authentication required" });
}
try {
const decoded = await server.jwt.verify(token);
request.user = decoded;
} catch (error) {
return reply.status(401).send({ message: "Authentication required" });
}
}
);
// Register routes
server.register(teacherRoutes, { prefix: "api/teachers" });
server.get("/healthcheck", async function () {
return { status: "OK" };
});
export default server;
This is my index.ts
import { teacherSchemas } from "./models/teacher/teacher.schema";
import server from "./server";
const start = async () => {
try {
for (const schema of teacherSchemas) {
server.addSchema(schema);
}
await server.listen({ port: 4000, host: "0.0.0.0" });
server.log.info("Server listening on port 4000");
} catch (error) {
server.log.error(error);
process.exit(1);
}
};
start();
And this is my teacher.route.ts:
import { FastifyInstance } from "fastify";
import {
getTeachersHandler,
loginTeacherHandler,
registerTeacherHandler,
} from "./teacher.controller";
import { $ref } from "./teacher.schema";
const teacherRoutes = async (server: FastifyInstance) => {
server.post(
"/",
{
schema: {
body: $ref("createTeacherRequestSchema"),
response: { 201: $ref("createTeacherResponseSchema") },
},
},
registerTeacherHandler
);
server.post(
"/login",
{
schema: {
body: $ref("loginTeacherRequestSchema"),
response: { 200: $ref("loginTeacherReplySchema") },
},
},
loginTeacherHandler
);
server.get("/", { preHandler: [server.authenticate] }, getTeachersHandler);
};
export default teacherRoutes;
create src/@types/fastify.d.ts and remember add it in typeRoots
content file:
in typescript.json