Skip to content

Writing more adapters ​

I sold you the fact it would be easy to write adapters for any framework. How about we verify that instead of just taking my word?

Express ​

ts
// src/server-first/8-writing-more-adapters/express-adapter.ts

import express from 'express'
import bodyParser from 'body-parser'
import { ServerAdapter } from '../7-grocery-list-project/server-adapter'
import { IncomingHttpHeaders } from 'http'
import { ReadableStream } from 'node:stream/web'

// See http://expressjs.com/en/5x/api.html
export const createExpressNodeServer: ServerAdapter = ({ handlers, port }) => {
  const app = express()
  app.use(bodyParser.urlencoded({ extended: true }))

  for (const { handle, ...route } of handlers) {
    const routerFn = route.method === 'GET' ? app.get : app.post

    routerFn.bind(app)(route.path, async (req, res) => {
      const response = await handle({
        body: req.body,
        headers: getHeadersFromIncomingHttpHeaders(req.headers),
        params: req.params,
        query: req.query,
      })
      response.headers.forEach((value, name) => res.setHeader(name, value))
      response.body
        ? res.status(response.status).send(await response.text())
        : res.sendStatus(response.status)
    })
  }

  return new Promise<void>((resolve) => {
    app.listen(port, resolve)
  })
}

function getHeadersFromIncomingHttpHeaders(incoming: IncomingHttpHeaders) {
  const headers = new Headers()
  Object.entries(incoming).forEach(([name, value]) => {
    const values = Array.isArray(value) ? value : [value]
    values.forEach((value) => {
      if (value) headers.append(name, value)
    })
  })
  return headers
}

Hapi.js ​

ts
// src/server-first/8-writing-more-adapters/hapi-adapter.ts

import { Server } from '@hapi/hapi'
import { ServerAdapter } from '../7-grocery-list-project/server-adapter'

// See https://hapi.dev/tutorials/
export const createHapiNodeServer: ServerAdapter = ({ port, handlers }) => {
  const server = new Server({ port })

  for (const { handle, ...route } of handlers) {
    server.route({
      method: route.method,
      path: adaptPath(route.path),
      handler: async (request, h) => {
        const response = await handle({
          body: request.payload as Record<string, any> | undefined,
          headers: new Headers(request.headers),
          params: request.params,
          query: { ...request.query },
        })

        const hapiRes = h
          .response((await response.text()) || undefined)
          .code(response.status)
        response.headers.forEach((value, name) => hapiRes.header(name, value))
        return hapiRes
      },
    })
  }

  return server.start()
}

/**
 * transforms "/todo/:id" to "/todo/{id}"
 */
function adaptPath(path: string) {
  return path.replace(/:([^/]+)/g, '{$1}')
}

Hono ​

ts
// src/server-first/8-writing-more-adapters/hono-adapter.ts

import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import { ServerAdapter } from '../7-grocery-list-project/server-adapter'

// See https://hono.dev/docs/getting-started/nodejs
export const createHonoNodeServer: ServerAdapter = ({ handlers, port }) => {
  const app = new Hono()

  for (const { handle, ...route } of handlers) {
    const routerFn = route.method === 'GET' ? app.get : app.post

    routerFn.bind(app)(route.path, async (c) => {
      return handle({
        body: await c.req.parseBody(),
        headers: new Headers(c.req.header()),
        params: c.req.param(),
        query: c.req.query(),
      })
    })
  }

  return new Promise<unknown>((resolve) => {
    serve({ fetch: app.fetch, port }, resolve)
  })
}

Exposing them all ​

ts
// src/server-first/8-writing-more-adapters/servers.ts

import { createExpressNodeServer } from './express-adapter'
import { makeHandlers } from './handlers'
import { createHapiNodeServer } from './hapi-adapter'
import { createHonoNodeServer } from './hono-adapter'

async function createServers() {
  const handlers = makeHandlers()
  await Promise.all([
    createHonoNodeServer({ port: 6001, handlers }),
    createExpressNodeServer({ port: 6002, handlers }),
    createHapiNodeServer({ port: 6003, handlers }),
  ])
  console.info(`Hono    server listening on http://localhost:6001`)
  console.info(`Express server listening on http://localhost:6002`)
  console.info(`Hapi    server listening on http://localhost:6003`)
}

createServers().catch(console.error)
sh
$: npx tsx src/server-first/8-writing-more-adapters/servers.ts
Hono    server listening on http://localhost:6001
Express server listening on http://localhost:6002
Hapi    server listening on http://localhost:6003

Yey, let’s test'hem all … it took me some (back-ported) adjustments, but here we go. All good βœ… πŸ’ͺ