NodeJs、Apollo 和 typescript 服务器挂在会话存储上

NodeJs, Apollo, and typescript server hang on session storage

我是 NodeJs 的新手,我正在学习 Ben Awad 制作的全栈教程 https://www.youtube.com/watch?v=I6ypD7qv3Z8&t=7186s

设置服务器后一切正常。我为会话存储添加了 express-session,并使用 Redis-connect 和 Redis 客户端将其与 Redis 链接。

这是我的 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));

还有我的 types.ts 文件:

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;
    }
  ];
};

还有我的 user.ts 解析器,我认为问题出现在登录突变中:

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 };
  }
}

我正在使用 Node LTS(目前为 16.13.1),这是 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"
    ]
  }
}

向 localhost:4000/graphql 发出请求并调用登录突变后,请求挂起,如果我尝试刷新页面,客户端无法连接到 localhost:4000/graphql,直到我删除cookie 缓存。

我已经在 Chrome 中尝试过使用两个帐户,Firefox、Edge、Apollo Studio、Thunder Client 和 Postman;并且都产生相同的行为。

值得注意的是,我正在使用 WSL2 和 Ubuntu 发行版,但我将我的项目移至 Windows,但同样的问题仍然存在。

非常感谢任何帮助我是 Node 的新手,在发布之前我已经搜索了这个问题 4 天。谢谢。

redis的问题。对于较新的版本,它以不同的方式保存密钥。

如果你想使用与Ben相同的代码,你必须使用legacyMode:

import { createClient } from 'redis';
const RedisStore = connectRedis(session);
const redisClient = createClient({ legacyMode: true });
await redisClient.connect();