Express (TypeScript) で作った local 開発用に立てた実験用サーバーに express-session
モジュールを使ってセッションを使えるようにしたのメモ
環境
- express@4.18.1
- graphql@16.5.0
- express-graphql@0.12.0
- express-session@1.17.3
- typescript@4.7.2
express-sesssion
Warning The default server-side session storage, MemoryStore, is purposely not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing.
cf. express-session - npm
ただし express-sesssion
はメモリ上に保存されるあくまで開発用とトノコト。
ts-node-dev
でホットリロードにしている場合サーバーが再起動するとセッションに保存していた内容が消えます
パッケージのインストール
$ npm i express-session # TypeScript 用の型もインストールする $ npm i -D @types/express-session
express-session でセッションを使う
server.ts
import express from 'express'; import session, { SessionOptions } from 'express-session'; const app = express(); // session const sess: SessionOptions = { // dummy secret: 'session_secret', // 1min = 60 * 1000 cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false, }; app.use(session(sess));
⚠ resave
と saveUninitialized
のオプションは未だ理解しきれてませんがデフォルトでの使用は非推奨とのことだったので、まぁサンプルだし動作してるから…ということで両方 false
にしました
Session に値を保存する
ドキュメントには req.session.views
というプロパティを使ってセッションに値を保存する例が載っていましたが、TypeScript だとそのままでは views
が未定義というエラーになってしまいました
server.ts
import express, { Request } from 'express'; import { buildSchema } from 'graphql'; import { graphqlHTTP, GraphQLParams } from 'express-graphql'; import session, { SessionOptions } from 'express-session'; const schema = buildSchema(` type Mutation { increment: Counter } type Counter { count: Int! } `); type Counter = { count: number; }; const root = { increment: (_: GraphQLParams['variables'], req: Request): Counter => { const count = req.session.views || 0; // => Property 'views' does not exist on type 'Session & Partial<SessionData>'. req.session.views = count + 1; return { count: req.session.views, }; }, } const app = express(); // 略 app.use(session(sess)); app.use( '/graphql', graphqlHTTP({ schema, rootValue: root, graphiql: true, }) );
=> Property 'views' does not exist on type 'Session & Partial<SessionData>'.
SessionData の型定義をすればOK
GitHub の issue に解決方法が載っていました
server.ts
に下記の定義を追加すればOK
declare module "express-session" { interface SessionData { views: any; } }
これで req.session.views
の TypeError が解消されました!
TypeScript なのだから型が決まった独自プロパティをもたせてしまったほうが良い
declare module "express-session" { interface SessionData {} }
は型定義なので views
以外の独自プロパティも設定することが可能なので views: any
より count: number
のように型エラーやエディタの保管が効く型を設定するほうが TypeScript の恩恵を受けられます
server.ts
declare module "express-session" { interface SessionData { - views: any; + count: number; } } const root = { increment: (_: GraphQLParams['variables'], req: Request): Counter => { - const count = req.session.views || 0; + const count = req.session.count || 0; - req.session.views = count + 1; + req.session.count = count + 1; return { - count: req.session.views, + count: req.session.count, }; }, }
mutation で session にデータを保存する
mutation で送ったパラメーターを session に保存する例
server.ts
const schema = buildSchema(` type Query { user: User } type Mutation { login(username: String!): User logout: Boolean! } type User { username: String! } `); type UserType = { username: string; }; const root = { user: (_: GraphQLParams['variables'], req: Request): UserType => { const username = req.session?.userName; if (!username) { throw new Error('No logged in user!'); } return { username, }; }, // Mutation login: ({ username }: { username: string }, req: Request): UserType => { if (username === '') { throw new Error('User Name is required!'); } // set username req.session.userName = username; return { username, }; }, logout: (_: GraphQLParams['variables'], req: Request): Boolean => { req.session.userName = undefined; return true; }, }; // 略
👇 下記のクエリが動作していれば OK
username をセッションに保存
mutation Login($name: String!) { login(username:$name) { username } } // Query Variables { "name": "Kiriya Aoi" }
セッションの username を破棄
mutation Logout { logout }
セッションに保存されている username を取得。存在しなければエラーを返す
query User { user { username } }
セッションを利用した場合の実験もできるようになりました!
Express は Nest.js に比べて自由すぎるから大変だという話も聞きますが、自由だからこそサクッと実験をするにはとても良きフレームワークだな〜と改めて感じたのでした。らぶ… (ファイル1つで済ませられるし)
おわり。
[参考]