I am creating an apollo server v4 gql gateway with the express.js integration.
I have user typedefs and user resolvers working and creating a jwt when a user is created.
I then try to use my jwtMiddleware
middleware on the /graphql
endpoint so that it can validate a bearer token is being passed in to prevent unauthenticated requests to some other queries I have created.
In my index.ts:
import express, { Request, Response, Application } from 'express';
import gqlServerV1 from './gql/v1/gqlServerV1';
import { mongoClient } from './mongodb/mongoclient';
import cors from 'cors';
import pkg from 'body-parser';
import { expressMiddleware } from '@apollo/server/express4';
import jwtMiddleware from './middleware/verifyJwt';
const { json } = pkg;
// Create express app
const app: Application = express();
const port: number = 5950;
const startUp = async () => {
// Fire up mongodb client
await mongoClient();
// Apply gql server to work with express as middleware w/ apollo-server v4
// Migrate away from v3: https://www.apollographql.com/docs/apollo-server/migration/#migrate-from-apollo-server-express
await gqlServerV1.start();
app.use(
'/v1/graphql',
cors<cors.CorsRequest>(),
json(),
jwtMiddleware,
expressMiddleware(gqlServerV1, {
context: async ({ req }) => ({
token: req.headers.token,
user: req.user,
}),
})
);
console.log(`server started at https://localhost:${port}`);
};
app.listen(port, async () => {
await startUp();
});
jwtMiddleware file
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
dotenv.config();
const JWT_SECRET = process.env.JWT_SECRET || 'defaultSecret';
const jwtMiddleware = async (req, res, next, context) => {
const authorizationHeader = req.headers.authorization;
console.log('heress');
if (!authorizationHeader || !authorizationHeader.startsWith('Bearer ')) {
// Token is missing or in an invalid format
return next();
}
const token = authorizationHeader.replace('Bearer ', '');
try {
// Verify the JWT token
const decodedToken = jwt.verify(token, JWT_SECRET);
console.log(decodedToken);
// Get the user data from the request context
const user = context.user;
// Continue processing the request if the user is authenticated
if (user) {
next();
} else {
// The user is not authenticated, so return an error
return res.status(401).json({ message: 'Unauthorized' });
}
} catch (error) {
// The JWT token is invalid, so return an error
return res.status(401).json({ message: 'Invalid token' });
}
};
export default jwtMiddleware;
The issue I am facing:
when starting my server I see this error coming from index.ts
when passing req.user
TSError: ⨯ Unable to compile TypeScript:
src/index.ts:37:19 - error TS2339: Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
37 user: req.user,
- I never see any console logs from my middleware being output.
This is my gql server in a separate file:
import { ApolloServer } from '@apollo/server';
import addressTypeDefs from './typeDefs/addressTypeDefs';
import userTypeDef from './typeDefs/userTypeDef';
import resolvers from './resolvers';
interface MyContext {
token?: String;
user?: Object;
}
// Create gql server
const gqlServerV1 = new ApolloServer<MyContext>({
typeDefs: [addressTypeDefs, userTypeDef],
resolvers,
introspection: false,
});
export default gqlServerV1;
package json scripts:
"scripts": {
"start": "ts-node-dev --watch src src/index.ts",
"dev:server": "nodemon --watch './**/*.ts' --exec 'node --experimental-specifier-resolution=node --loader ts-node/esm' src/index.ts",
"test": "jest --detectOpenHandles --config jest.config.ts ./__tests__",
"watch": "nodemon --watch './**/*.{ts,graphql}' --exec 'node --experimental-specifier-resolution=node --no-warnings --loader ts-node/esm' src/index.ts",
"dev": "tsc-watch --onSuccess "npm run watch""
},
I also have a custom.d.ts file to try to extend express:
interface JwtPayload {
// Customize the properties based on JWT payload
email: string;
}
// Extend the express request type
declare namespace Express {
interface Request {
user: JwtPayload;
}
}
- How do I properly pass a
req.user
? - Why is my middleware never running to validate the user/token?
Thanks!
1 Answer
I figured out the problem and will leave the question posted so others can learn. The issue I was facing was because ts-node was not able to interpret the type I was extending and adding user to i.e. the request. Here’s what I did.
- Create a new dir and d.ts file at
root/src/@types/express/index.d.ts
. - In ts config make sure I have my custom types come before any others in the
typeRoot
config. Like so:
{
"compilerOptions": {
"rootDirs": ["src"],
"outDir": "dist",
"lib": ["es2020"],
"target": "es2020",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"types": ["node", "jest", "express"],
"typeRoots": ["./src/@types", "./node_modules/@types"],
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["src/**/*.ts"]
}
- Inside of the
index.d.ts
I imported a custom interface I have set up in mymodels
from mongodb and set that as the type like so:
import * as express from 'express';
import { User } from '../../models/User';
declare global {
namespace Express {
interface Request {
user?: User;
}
}
}
- In my verifyJwt middleware, I removed
context
from the middleware signature and replaced instances ofcontext
withreq
instead.
After this, I restarted the ts server as well as my local server and Voila, it worked and the dumb Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.
error was gone. AND my middleware began working and I started seeing console logs from the middleware as verification.
Hope this helps someone else.