JWT Tutorial w/ Node.js, GraphQL, React
動画のまとめ
サーバ側
typeorm
とtype-graphql
を使うと、GraphQL の object type の作成と、ORM のモデル(エンティティー)の作成を、一つのクラス定義により行うことができる。
プロジェクトのセットアップ
npx typeorm init --name server --database postgres
- postgres で DB を手動作成
ormconfig.json
のユーザ名などを適宜編集yarn start
で自動的にテーブル等が作成される
graphql 関係
yarn add express apollo-server-express graphql type-graphql
yarn add -D @types/express @types/graphql
Resolver
// UserResolver.ts
import { Query, Resolver } from 'type-graphql';
@Resolver()
export class UserResolver {
@Query(() => String) // resolverにおいてquery typeの宣言も同時に行える
hello() {
return 'hi!';
}
}
// index.ts
import { buildSchema } from 'type-graphql';
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [UserResolver],
}),
});
Mutation
yarn add bcryptjs
yarn add -D @types/bcryptjs
export class UserResolver {
@Mutation(() => Boolean) // 成功したかどうかを返す
async register(
@Arg('email') email: string, // クエリ時の引数名、格納する変数名と型
@Arg('password') password: string,
) {
const hashedPassword = await hash(password, 12);
try {
await User.insert({
email,
password: hashedPassword,
});
} catch (err) {
console.log(err);
return false;
}
return true;
}
}
Query
users の一覧取得クエリ
// entity/User.ts
import { Field, ObjectType } from 'type-graphql';
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@ObjectType() // GraphQLでObject Typeとして使いますよ
@Entity('user') // テーブル名を指定
// BaseEntityを継承することでActive Recordパターンが使えるようになる
export class User extends BaseEntity {
@Field() // クエリ時に公開するフィールドですよ
@PrimaryGeneratedColumn()
id: number;
@Field()
@Column('text') // DBカラムの値種別 なくてもいい
email: string;
// このフィールドはクエリ時に公開しない
@Column()
password: string;
}
// UserResolver.ts
@Resolver()
export class UserResolver {
@Query(() => [User])
users() {
return User.find();
}
JWT
yarn add jsonwebtoken
yarn add @types/jsonwebtoken
import { compare, hash } from 'bcryptjs';
import { sign } from 'jsonwebtoken';
import {
Arg,
Field,
Mutation,
ObjectType,
Query,
Resolver,
} from 'type-graphql';
import { User } from './entity/User';
// graphqlの型としても使うし、TypeScriptの型としても使う
@ObjectType()
class LoginRespone {
@Field()
accessToken: string;
}
@Resolver()
export class UserResolver {
// resolverにおいてmutation typeの宣言も同時に行える
@Mutation(() => LoginRespone)
async login(
@Arg('email') email: string,
@Arg('password') password: string,
): Promise<LoginRespone> {
const user = await User.findOne({ where: { email } });
if (!user) {
throw Error('could not find user');
}
const valid = await compare(password, user.password);
if (!valid) {
throw Error('bad password');
}
return {
accessToken: sign({ userId: user.id }, 'asdfasdfasd', {
expiresIn: '15m',
}),
};
}
}
Context
ApolloServer で express の req, res を取得したいときは context を使う
const apolloServer = new ApolloServer({
context: ({ req, res }) => ({ req, res }),
});
export class UserResolver {
@Mutation()
async login(@Ctx() { req, res }) {
// req, resを使ってなにかする
}
}
dotenv
yarn add dotenv
.env
ACCESS_TOKEN_SECRET=asdfasdfasdf
import 'dotenv/config';
console.log(process.env.ACCESS_TOKEN_SECRET);
type-graphql での認証機能の実装方法
// MyContext.ts
import { ExpressContext } from 'apollo-server-express/dist/ApolloServer';
export interface MyContext extends ExpressContext {
// これに任意のデータを乗せる。名前は自由。
payload?: { userId: string };
}