从 Express.js 到 H3 Express to H3

通过各种示例,让我们看看如果您熟悉 Express.js,使用 h3 是多么容易。

在本指南中,我们将重现 Express.js 文档 中的许多示例,向您展示如何使用 h3 做同样的事情。

如果您不熟悉 Express.jNumber.isNaNr.isNaNr.isNaNu,可以放心跳过本指南。 目的是向您展示 h3 与 Express.js 有多么相似。一旦您了解了相似之处,如果您熟悉 Express.js,您将能够毫无问题地使用 h3。

即使 h3 看起来与 Express.js 相似,但这并不意味着 Express.js 仍然可行。Express.js 是一个很久没有发展的老框架。对于新项目来说,这不是一个好的选择,因为它很容易导致安全问题和内存泄漏。

使用 h3,您还可以使用 unjs/listhen 无需任何配置即可开箱即用地重新加载。

您可以使用 npx --yes listhen -w ./app.ts 运行每个 h3 示例。 :

Hello World

Express.js 文档中的第一个示例是 Hello World

代码非常简单:

index.js
js
/**
 * Express.js 示例应用程序。
 */
var express = require('express')
var app = express()

app.get('/', (req, res) => {
  res.send('Hello World')
})

app.listen(3000)
console.log('Express 在端口 3000 上启动')

让我们看看如何使用 h3 做同样的事情:

app.ts
ts
/**
 * h3 示例应用程序。
 */
import { createApp, defineEventHandler } from 'h3'

export const app = createApp()

app.use(
  '/',
  defineEventHandler((event) => {
    return 'Hello World'
  }),
)

然后,您可以使用 npx --yes listhen -w ./app.ts 启动服务器并转到 http://localhost:3000 查看结果。

相关阅读:guide > app

多路由器

第二个示例是 多路由器。在这个例子中,我们创建了多个路由器来拆分逻辑。

index.js
js
/**
 * Express.js example app.
 */
var express = require('express')

var app = express()

var apiv1 = express.Router()

apiv1.get('/', (req, res) => {
  res.send('Hello from APIv1 root route.')
})

apiv1.get('/users', (req, res) => {
  res.send('List of APIv1 users.')
})

const apiv2 = express.Router()

apiv2.get('/', (req, res) => {
  res.send('Hello from APIv2 root route.')
})

apiv2.get('/users', (req, res) => {
  res.send('List of APIv2 users.')
})

app.use('/api/v1', apiv1)
app.use('/api/v2', apiv2)

app.get('/', (req, res) => {
  res.send('Hello from root route.')
})

app.listen(3000)
console.log('Express started on port 3000')

对于某些设施,我们将每个文件归入同一个组。

使用 h3,我们可以做同样的事情:

app.ts
ts
/**
 * h3 example app.
 */
import { createApp, createRouter, defineEventHandler, useBase } from 'h3'

export const app = createApp()

const apiv1 = createRouter()
  .get(
    '/',
    defineEventHandler(() => {
      return 'Hello from APIv1 root route.'
    }),
  )
  .get(
    '/users',
    defineEventHandler(() => {
      return 'List of APIv1 users.'
    }),
  )

const apiv2 = createRouter()
  .get(
    '/',
    defineEventHandler(() => {
      return 'Hello from APIv2 root route.'
    }),
  )
  .get(
    '/users',
    defineEventHandler(() => {
      return 'List of APIv2 users.'
    }),
  )

app.use('/api/v1/**', useBase('/api/v1', apiv1.handler))
app.use('/api/v2/**', useBase('/api/v2', apiv2.handler))

非常相似。主要区别在于我们必须使用“useBase”来定义路由器的基本路径。

相关阅读:guide > router

参数

第三个例子是 Params。在这个例子中,我们在路由中使用参数。

index.js
js
/**
 * Express.js example app.
 */
var createError = require('http-errors')
var express = require('express')
var app = express()

var users = [
  { name: 'tj' },
  { name: 'tobi' },
  { name: 'loki' },
  { name: 'jane' },
  { name: 'bandit' },
]

app.param(['to', 'from'], (req, res, next, num, name) => {
  req.params[name] = Number.parseInt(num, 10)
  if (Number.isNaN(req.params[name])) {
    next(createError(400, `failed to parseInt ${num}`))
  }
  else {
    next()
  }
})

app.param('user', (req, res, next, id) => {
  if ((req.user === users[id])) {
    next()
  }
  else {
    next(createError(404, 'failed to find user'))
  }
})

app.get('/', (req, res) => {
  res.send('Visit /user/0 or /users/0-2')
})

app.get('/user/:user', (req, res) => {
  res.send(`user ${req.user.name}`)
})

app.get('/users/:from-:to', (req, res) => {
  var from = req.params.from
  var to = req.params.to
  var names = users.map((user) => {
    return user.name
  })
  res.send(`users ${names.slice(from, to + 1).join(', ')}`)
})

app.listen(3000)
console.log('Express started on port 3000')

使用 h3,我们可以做同样的事情:

app.ts
ts
/**
 * h3 example app.
 */
import {
  createApp,
  createError,
  createRouter,
  defineEventHandler,
  getRouterParam,
  getValidatedRouterParams,
} from 'h3'
import { z } from 'zod'

const users = [
  { name: 'tj' },
  { name: 'tobi' },
  { name: 'loki' },
  { name: 'jane' },
  { name: 'bandit' },
]

export const app = createApp()
const router = createRouter()

router.get(
  '/',
  defineEventHandler(() => {
    return 'Visit /users/0 or /users/0/2'
  }),
)

router.get(
  '/user/:user',
  defineEventHandler(async (event) => {
    const { user } = await getValidatedRouterParams(
      event,
      z.object({
        user: z.number({ coerce: true }),
      }).parse,
    )

    if (!users[user]) {
      throw createError({
        status: 404,
        statusMessage: 'User Not Found',
      })
    }

    return `user ${user}`
  }),
)

router.get(
  '/users/:from/:to',
  defineEventHandler(async (event) => {
    const { from, to } = await getValidatedRouterParams(
      event,
      z.object({
        from: z.number({ coerce: true }),
        to: z.number({ coerce: true }),
      }).parse,
    )

    const names = users.map((user) => {
      return user.name
    })

    return `users ${names.slice(from, to).join(', ')}`
  }),
)

app.use(router)

使用 h3,我们没有 param 方法。相反,我们使用 getRouterParamgetValidatedRouterParams 来验证参数。

它更明确且更易于使用。在此示例中,我们使用 Zod,但您可以自由使用任何其他验证库。

Cookies

第四个示例是 Cookies。在此示例中,我们使用 cookies。

index.js
js
/**
 * Express.js example app.
 */
var express = require('express')
var app = express()
var cookieParser = require('cookie-parser')

app.use(cookieParser('my secret here'))

app.use(express.urlencoded({ extended: false }))

app.get('/', (req, res) => {
  if (req.cookies.remember) {
    res.send('Remembered :). Click to <a href="/forget">forget</a>!.')
  }
  else {
    res.send(
      '<form method="post"><p>Check to <label>'
      + '<input type="checkbox" name="remember"/> remember me</label> '
      + '<input type="submit" value="Submit"/>.</p></form>',
    )
  }
})

app.get('/forget', (req, res) => {
  res.clearCookie('remember')
  res.redirect('back')
})

app.post('/', (req, res) => {
  var minute = 60000
  if (req.body.remember)
    res.cookie('remember', 1, { maxAge: minute })
  res.redirect('back')
})

app.listen(3000)
console.log('Express started on port 3000')

使用 h3,我们可以做同样的事情:

app.ts
ts
import {
  createApp,
  createRouter,
  defineEventHandler,
  getCookie,
  getHeader,
  readBody,
  sendRedirect,
  setCookie,
} from 'h3'

export const app = createApp()
const router = createRouter()

router.get(
  '/',
  defineEventHandler((event) => {
    const remember = getCookie(event, 'remember')

    if (remember) {
      return 'Remembered :). Click to <a href="/forget">forget</a>!.'
    }
    else {
      return `<form method="post"><p>Check to <label>
    <input type="checkbox" name="remember"/> remember me</label>
    <input type="submit" value="Submit"/>.</p></form>`
    }
  }),
)

router.get(
  '/forget',
  defineEventHandler((event) => {
    deleteCookie(event, 'remember')

    const back = getHeader(event, 'referer') || '/'
    return sendRedirect(event, back)
  }),
)

router.post(
  '/',
  defineEventHandler(async (event) => {
    const body = await readBody(event)

    if (body.remember)
      setCookie(event, 'remember', '1', { maxAge: 60 * 60 * 24 * 7 })

    const back = getHeader(event, 'referer') || '/'
    return sendRedirect(event, back)
  }),
)

app.use(router)

使用 h3,我们没有 cookieParser 中间件。相反,我们使用 getCookiesetCookie 来获取和设置 cookie。它更明确,更易于使用。

中间件

使用 express 时,我们通常使用 middleware 来处理请求。

例如,这里我们使用 morgan 来处理请求日志记录。

index.js
js
var express = require('express')
var morgan = require('morgan')

var app = express()

app.use(morgan('combined'))

app.get('/', (req, res) => {
  res.send('hello, world!')
})

app.listen(3000)
console.log('Express started on port 3000')

h3 中,我们还可以直接使用来自 express 生态系统的中间件。

通过使用 fromNodeMiddleware 进行包装,可以轻松实现这一点。

app.ts
ts
import morgan from 'morgan'
import { createApp, defineEventHandler, fromNodeMiddleware } from 'h3'

export const app = createApp()

app.use(fromNodeMiddleware(morgan('combined')))

app.use(
  '/',
  defineEventHandler((event) => {
    return 'Hello World'
  }),
)