I'm new to NodeJs, and I'm following the full-stack tutorial made by Ben Awad https://www.youtube.com/watch?v=I6ypD7qv3Z8&t=7186s.
After setting up my server and everything works fine. I added express-session for session storage and linked it with Redis using Redis-connect and Redis client.
This is my index.ts:
import { MikroORM } from "@mikro-orm/core";
import { ApolloServer } from "apollo-server-express";
import connectRedis from "connect-redis";
import express from "express";
import session from "express-session";
import { createClient } from "redis";
import "reflect-metadata";
import { buildSchema } from "type-graphql";
import { __prod__ } from "./constants";
import mikroOrmConfig from "./mikro-orm.config";
import { PostResolver } from "./resolvers/post";
import { UserResolver } from "./resolvers/user";
import { MyContext } from "./types";
const main = async () => {
const orm = await MikroORM.init(mikroOrmConfig);
await orm.getMigrator().up();
const app = express();
const RedisStore = connectRedis(session);
const redisClient = createClient();
await redisClient.connect();
await redisClient.ping();
redisClient.on("error", err => console.log("Redis Client Error", err));
app.use(
session({
name: "qid",
store: new RedisStore({ client: redisClient, disableTouch: true }),
secret: "qsfniuiqsdnigu",
saveUninitialized: false,
resave: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 10,
httpOnly: true,
sameSite: "lax",
secure: __prod__,
},
})
);
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [UserResolver, PostResolver],
validate: false,
}),
context: ({ req, res }): MyContext => ({ em: orm.em, req, res }),
});
await apolloServer.start();
apolloServer.applyMiddleware({ app });
app.listen(4000, () => {
console.log(`listening on http://localhost:${4000}`);
});
};
main().catch(err => console.error("Errors: ", err));
And my types.ts file:
import { Connection, EntityManager, IDatabaseDriver } from "@mikro-orm/core";
import { Request, Response } from "express";
declare module "express-session" {
interface Session {
userId: number;
}
}
export type MyContext = {
em: EntityManager<IDatabaseDriver<Connection>>;
req: Request;
res: Response;
};
export type FieldError = {
errors: [
{
field: string;
message: string;
}
];
};
And my user.ts resolver where I think the problem occurs in Login mutation:
import argon2 from "argon2";
import {
Arg,
Ctx,
Field,
InputType,
Mutation,
ObjectType,
Resolver,
} from "type-graphql";
import { User } from "../entities/User";
import { MyContext } from "../types";
@InputType()
class LoginInputs {
@Field()
username: string;
@Field()
password: string;
}
@ObjectType()
class FieldError {
@Field()
field: string;
@Field()
message: string;
}
@ObjectType()
class UserResponse {
@Field(() => [FieldError], { nullable: true })
errors?: FieldError[];
@Field(() => User, { nullable: true })
user?: User;
}
@Resolver()
export class UserResolver {
@Mutation(() => UserResponse)
async login(
@Arg("options") options: LoginInputs,
@Ctx() { em, req }: MyContext
): Promise<UserResponse> {
const { username, password } = options;
const user = await em.findOne(User, { username });
const errors = {
errors: [
{
field: "username or password",
message: "There is no user with the given credentials",
},
],
};
if (!user) {
return errors;
}
const valid = await argon2.verify(user.password, password);
if (!valid) {
return errors;
}
req.session.userId = user.id; // If I comment this line everything works fine
return { user };
}
}
I'm using Node LTS (16.13.1 for now), and this is package.json:
{
"name": "lirredit-backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"watch": "tsc -w",
"dev": "nodemon dist/index.js",
"start": "node dist/index.js",
"create:migration": "mikro-orm migration:create"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/connect-redis": "^0.0.18",
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.4",
"@types/node": "^17.0.14",
"nodemon": "^2.0.15",
"ts-node": "^10.4.0",
"typescript": "^4.5.5"
},
"dependencies": {
"@mikro-orm/cli": "^4.5.10",
"@mikro-orm/core": "^4.5.10",
"@mikro-orm/migrations": "^4.5.10",
"@mikro-orm/postgresql": "^4.5.10",
"apollo-server-core": "^3.6.2",
"apollo-server-express": "^3.6.2",
"argon2": "^0.28.4",
"class-validator": "^0.13.2",
"connect-redis": "^6.0.0",
"cors": "^2.8.5",
"express": "^4.17.2",
"express-session": "^1.17.2",
"graphql": "15.7.2",
"pg": "^8.7.1",
"redis": "^4.0.3",
"reflect-metadata": "^0.1.13",
"type-graphql": "^1.1.1"
},
"mikro-orm": {
"useTsNode": true,
"configPaths": [
"./src/mikro-orm.config.ts",
"./dist/mikro-orm.config.js"
]
}
}
After making a request to localhost:4000/graphql and calling the Login mutation the request hangs and if I tried to refresh the page the client could not connect to localhost:4000/graphql until I delete the cookies cache.
I've tried it in Chrome with two accounts, Firefox, Edge, Apollo Studio, Thunder Client, and Postman; and all producing the same behavior.
It's worth noting that I'm am using WSL2 and Ubuntu distribution but I moved my project to Windows and the same problem persists.
Any help is really appreciated I'm new to Node and I've searched this issue for 4 days now before posting. Thanks.
The problem is redis. With newer versions it saves the keys differently.
If you want to use the same code as Ben, you have to use legacyMode:
import { createClient } from 'redis';
const RedisStore = connectRedis(session);
const redisClient = createClient({ legacyMode: true });
await redisClient.connect();