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です。次はユーザー認証の応用機能を実装していきます。