TypeGraphQL -ユーザー認証 Part1-
Register resolver の作成
まずはユーザー認証のregister(登録)からコードをかいていきます。src下にuser/Register.tsを作成します。またパスワードはハッシュ化させるのでbcryptjsをインストールしておいてください。
// src/user/Register.ts
import bcrypt from 'bcryptjs'
import { User } from "../entity/User";
import { Arg, Mutation, Resolver } from "type-graphql";
@Resolver()
export class RegisterResolver{
@Mutation(() => User)
async register(
@Arg("firstname") firstname: string,
@Arg("lastname") lastname: string,
@Arg("email") email: string,
@Arg("password") password: string
): Promise<User>{
const hashedPassword = await bcrypt.hash(password, 12)
const user = await User.create({
firstname,
lastname,
email,
password: hashedPassword
}).save()
return user;
}
}
前回、src/index.tsで作成したresolverのように先頭に@Resolver()をつけ、クラス名はRegisterResolverとします。type-graphでは@Arg("firstname") firstname: stringのように引数のargをわたします。
GraphQLのPlaygroundにいく前にentity/User.tsを変える必要があります。今のentityではGraphQLのOutputに対応していません。
// entity/User.ts
import { Field, ID, ObjectType } from "type-graphql";
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm";
@ObjectType()
@Entity()
export class User extends BaseEntity {
@Field(() => ID)
@PrimaryGeneratedColumn()
id: number;
@Field()
@Column()
firstname: string;
@Field()
@Column()
lastname: string;
@Field()
@Column("text", { unique: true })
email: string;
@Column()
password: string;
}
@Fieldを各フィールドにたすことでGraphQLが値を返すことができます。ここではパスワードをGraphQLにわたす必要がないので@Fieldはつけません。また、idフィールドはnumber型を持ちますがGraphQLにnumber型はないのでIDをかえすように@Field内にかきます。最後にsrc/index.tsのスキーマに作成したRegisterResolverを下記のようにSchameに加えます。
const schema = await buildSchema({
resolvers: [HelloResolver, RegisterResolver]
});
サーバーをはしらせてPlaygroundでregister関数を試してみましょう。下のように作成したユーザーがかえってきたらLogin(ログイン)にすすみましょう。
Login resolver の作成
最初にいくつかのパッケージをインストールします。
yarn add express-session connect-redis ioredis
yarn add -D @types/express-session @types/connect-redis @types/ioredis
Redisはインメモリ型データ構造ストアであり、複雑な型のデータを操作し、保存することができる。そして各種アプリケーションからの高速アクセスを可能にしてくれる。くわしい説明とインストール
またioredisはRedisと接続してくれて、Promise 対応しているのでNode.jsと相性がいいです。ここではセッション情報をRedisに保存します。
セッションやRedisを扱うコードをsrc/index.tsに先にかいていきます。
// src/index.ts
import "reflect-metadata"
import { ApolloServer } from "apollo-server-express";
import Express from "express";
import { buildSchema } from "type-graphql";
import { createConnection } from 'typeorm'
import { RegisterResolver } from "./user/Register";
import session from 'express-session';
import connectRedis from 'connect-redis'
import { redis } from './redis'
const serverStart = async () => {
await createConnection();
const schema = await buildSchema({
resolvers: [RegisterResolver]
});
const server = new ApolloServer({
schema,
context: ({ req }: any) => ({ req })
});
const app = Express();
const RedisStore = connectRedis(session)
app.use(
session({
store: new RedisStore({
client: redis as any
}),
name: "typeorm",
secret: "aslkdfjoiq12312",
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 1000 * 60 * 60 * 24 * 7 * 365
}
})
);
server.applyMiddleware({ app });
app.listen(4000, () => {
console.log("🚀 server started on http://localhost:4000/graphql");
});
};
serverStart();
contextからセッションにアクセスするために下のコードのように書きます。
const server = new ApolloServer({
schema,
context: ({ req }: any) => ({ req })
});
connect-redisについてこちらのサイトでくわしい説明が載っています。
いよいよログイン機能を実装します。src下にuser/Login.tsをつくり、コードをかいていきます。
// user/Login.ts
import bcrypt from 'bcryptjs'
import { User } from "../entity/User";
import { Arg, Ctx, Mutation, Resolver } from "type-graphql";
import { MyContext } from '../MyContext'
@Resolver()
export class LoginResolver{
@Mutation(() => User, { nullable: true })
async login(
@Arg("email") email: string,
@Arg("password") password: string,
@Ctx() context: MyContext
): Promise<User | null>{
const user = await User.findOne({ where: { email }});
if(!user){
return null
}
const valid = await bcrypt.compare(password, user.password)
if(!valid){
return null
}
context.req.session.userId = user.id;
return user;
}
}
基本的にRegisterResolverと同じですが、セッションを使いたいので@Ctx() context: MyContextとcontextの引数を渡します。resolverの引数に関してはこちらのページにかいています。
MyContextに関しては以下のようにsrc下に定義しています。
// src/MyContext.ts
import { Request } from 'express'
export interface MyContext {
req: Request
}
セッションをcontext.req.session.userIdで受け取れ、user.idを代入しRedisにセッションを保存できます。
meQueryの作成
ログイン状態にあるユーザーのユーザー情報を先ほどRedisに保存したセッションを利用して取り出します。
src下にuser/Me.tsをつくります。
// src/user/Me.ts
import { User } from "../entity/User";
import { Ctx, Resolver, Query } from "type-graphql";
import { MyContext } from '../MyContext'
@Resolver()
export class MeResolver{
@Query(() => User, { nullable: true })
async me(
@Ctx() context: MyContext
){
if(!context.req.session.id){
return null
}
return User.findOne(context.req.session.userId)
}
}
最後にsrc/index.tsのSchemaにMeResolverを加えることを忘れないでください。
// src/index.ts
const schema = await buildSchema({
resolvers: [RegisterResolver, LoginResolver, MeResolver]
});
ログインしたユーザーがかえってくればOKです。次はユーザー認証の応用機能を実装していきます。