Skip to content

バックエンドフレームワーク

各バックエンドの例では、ルートコンテナを一度だけ構築し、HTTP リクエストごとに 1 つのリクエストスコープを作成し、フレームワークネイティブなリクエストオブジェクトを通じてスコープを公開し、レスポンスのライフサイクルの中でそれを破棄します。

これらは examples/_shared/container.ts を共有しているため、以下の違いはフレームワークのライフサイクルフックとアダプターの API にあります。

アダプター
fastify.ts@inferdi/fastify
hono.ts@inferdi/hono
koa.ts@inferdi/koa
express.ts@inferdi/express
elysia.ts@inferdi/elysia

Fastify

ts
import Fastify, { type FastifyInstance, type FastifyRequest } from 'fastify'
import { inferdiFastify } from '@inferdi/fastify'

import {
  buildRootContainer,
  createRequestScope,
  type RootContainer,
  type RequestContainer,
} from '../_shared/container.js'

const root = buildRootContainer()

declare module 'fastify' {
  interface FastifyInstance {
    di: RootContainer
  }

  interface FastifyRequest {
    di: RequestContainer
  }
}

function normalizeHeader(value: string | string[] | undefined): string | undefined {
  return Array.isArray(value) ? value[0] : value
}

export function buildServer(): FastifyInstance {
  const app = Fastify()

  app.register(inferdiFastify, {
    container: root,
    // Annotate hook params: `app.register` cannot infer the plugin's generics.
    createScope: (root: RootContainer, request: FastifyRequest) =>
      createRequestScope(root, {
        requestId: request.id,
        ip: request.ip,
        userId: normalizeHeader(request.headers['x-user-id']),
      }),
  })

  app.get('/users/:id', async (request) => {
    const { id } = request.params as { id: string }
    return request.di.get('users').profile(id)
  })

  return app
}

リポジトリのファイル: examples/backend/fastify.ts

Hono

ts
import { Hono } from 'hono'
import { inferdiHono, type InferdiHonoEnv } from '@inferdi/hono'

import { buildRootContainer } from '../_shared/container.js'

const root = buildRootContainer()
type AppEnv = InferdiHonoEnv<typeof root>

export const app = new Hono<AppEnv>()

app.use('*', inferdiHono({
  container: root,
  setupScope: (scope, c) => {
    const request = scope.get('request')
    request.requestId = crypto.randomUUID()
    request.userId = c.req.header('x-user-id')
  },
}))

app.get('/users/:id', async (c) => {
  const user = await c.var.di.get('users').profile(c.req.param('id'))
  return c.json(user)
})

リポジトリのファイル: examples/backend/hono.ts

Koa

ts
import Koa from 'koa'
import { inferdiKoa } from '@inferdi/koa'

import {
  buildRootContainer,
  createRequestScope,
  type RequestContainer,
} from '../_shared/container.js'

const root = buildRootContainer()

declare module 'koa' {
  interface DefaultState {
    di: RequestContainer
  }
}

export const app = new Koa()

app.use(inferdiKoa({
  container: root,
  createScope: (root, ctx) =>
    createRequestScope(root, {
      requestId: crypto.randomUUID(),
      ip: ctx.ip,
      userId: ctx.get('x-user-id') || undefined,
    }),
}))
app.use(async (ctx) => {
  const id = ctx.path.split('/').pop() ?? ''
  ctx.body = await ctx.state.di.get('users').profile(id)
})

リポジトリのファイル: examples/backend/koa.ts

Express

ts
import express from 'express'
import { inferdiExpress } from '@inferdi/express'

import {
  buildRootContainer,
  createRequestScope,
  type RequestContainer,
} from '../_shared/container.js'

const root = buildRootContainer()

declare global {
  namespace Express {
    interface Request {
      di: RequestContainer
    }
  }
}

function normalizeHeader(value: string | string[] | undefined): string | undefined {
  return Array.isArray(value) ? value[0] : value
}

export const app = express()

app.use(inferdiExpress({
  container: root,
  createScope: (root, req) =>
    createRequestScope(root, {
      requestId: crypto.randomUUID(),
      // req.ip is `string | undefined` in @types/express — propagate that shape.
      ip: req.ip,
      userId: normalizeHeader(req.headers['x-user-id']),
    }),
}))

app.get('/users/:id', async (req, res, next) => {
  try {
    res.json(await req.di.get('users').profile(req.params.id))
  } catch (error) {
    next(error)
  }
})

リポジトリのファイル: examples/backend/express.ts

Elysia

ts
import { Elysia } from 'elysia'
import { inferdiElysia } from '@inferdi/elysia'

import {
  buildRootContainer,
  createRequestScope,
} from '../_shared/container.js'

const root = buildRootContainer()

export const app = new Elysia()
  .use(inferdiElysia({
    container: root,
    createScope: (root, { request }) =>
      createRequestScope(root, {
        requestId: crypto.randomUUID(),
        userId: request.headers.get('x-user-id') ?? undefined,
      }),
  }))
  .get('/users/:id', ({ params, di }) =>
    di.get('users').profile(params.id),
  )

リポジトリのファイル: examples/backend/elysia.ts