第一个应用 #

一、Hello World #

让我们从最简单的Hello World开始,逐步了解Fastify的基本用法。

1.1 最简应用 #

创建 app.js

javascript
const fastify = require('fastify')()

fastify.get('/', async (request, reply) => {
  return { hello: 'world' }
})

fastify.listen({ port: 3000 }, (err, address) => {
  if (err) throw err
  console.log(`Server listening on ${address}`)
})

运行:

bash
node app.js

测试:

bash
curl http://localhost:3000

输出:

json
{"hello":"world"}

1.2 带日志的应用 #

javascript
const fastify = require('fastify')({
  logger: true
})

fastify.get('/', async (request, reply) => {
  return { hello: 'world' }
})

fastify.listen({ port: 3000 }, (err, address) => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
})

日志输出:

text
{"level":30,"time":1709500000000,"msg":"Server listening at http://127.0.0.1:3000"}
{"level":30,"time":1709500000100,"reqId":"req-1","req":{"method":"GET","url":"/"},"msg":"incoming request"}
{"level":30,"time":1709500000105,"reqId":"req-1","res":{"statusCode":200},"responseTime":5,"msg":"request completed"}

二、基础路由 #

2.1 定义路由 #

Fastify支持所有HTTP方法:

javascript
const fastify = require('fastify')({ logger: true })

fastify.get('/users', async (request, reply) => {
  return { method: 'GET', path: '/users' }
})

fastify.post('/users', async (request, reply) => {
  return { method: 'POST', path: '/users', body: request.body }
})

fastify.put('/users/:id', async (request, reply) => {
  return { method: 'PUT', path: '/users', id: request.params.id }
})

fastify.patch('/users/:id', async (request, reply) => {
  return { method: 'PATCH', path: '/users', id: request.params.id }
})

fastify.delete('/users/:id', async (request, reply) => {
  return { method: 'DELETE', path: '/users', id: request.params.id }
})

fastify.head('/users', async (request, reply) => {
  reply.code(200).send()
})

fastify.options('/users', async (request, reply) => {
  reply
    .code(200)
    .header('Allow', 'GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS')
    .send()
})

fastify.listen({ port: 3000 })

2.2 路由选项 #

完整的路由定义:

javascript
fastify.route({
  method: 'GET',
  url: '/users/:id',
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer' }
      }
    },
    response: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          name: { type: 'string' }
        }
      }
    }
  },
  preHandler: async (request, reply) => {
    console.log('Pre-handler executed')
  },
  handler: async (request, reply) => {
    return { id: request.params.id, name: 'John Doe' }
  }
})

2.3 路由简写 #

Fastify提供了路由简写方法:

javascript
fastify.get('/path', options, handler)
fastify.post('/path', options, handler)
fastify.put('/path', options, handler)
fastify.patch('/path', options, handler)
fastify.delete('/path', options, handler)
fastify.head('/path', options, handler)
fastify.options('/path', options, handler)
fastify.all('/path', options, handler)

三、请求处理 #

3.1 获取请求参数 #

javascript
fastify.get('/users/:id', async (request, reply) => {
  const { id } = request.params
  return { userId: id }
})

fastify.get('/search', async (request, reply) => {
  const { q, page, limit } = request.query
  return { query: q, page, limit }
})

fastify.post('/users', async (request, reply) => {
  const { name, email } = request.body
  return { name, email }
})

fastify.get('/info', async (request, reply) => {
  const userAgent = request.headers['user-agent']
  const authorization = request.headers.authorization
  return { userAgent, authorization }
})

3.2 参数验证 #

使用JSON Schema验证:

javascript
fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string', minLength: 2 },
        email: { type: 'string', format: 'email' },
        age: { type: 'integer', minimum: 0, maximum: 150 }
      }
    }
  }
}, async (request, reply) => {
  return { success: true, data: request.body }
})

验证失败响应:

json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "body must have required property 'name'"
}

3.3 路由参数验证 #

javascript
fastify.get('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer' }
      }
    }
  }
}, async (request, reply) => {
  return { userId: request.params.id }
})

fastify.get('/products/:category/:productId', {
  schema: {
    params: {
      type: 'object',
      required: ['category', 'productId'],
      properties: {
        category: { type: 'string' },
        productId: { type: 'integer' }
      }
    }
  }
}, async (request, reply) => {
  const { category, productId } = request.params
  return { category, productId }
})

四、响应处理 #

4.1 返回JSON #

javascript
fastify.get('/json', async (request, reply) => {
  return { message: 'Hello JSON' }
})

fastify.get('/json-explicit', async (request, reply) => {
  reply.send({ message: 'Hello JSON' })
})

4.2 设置状态码 #

javascript
fastify.get('/created', async (request, reply) => {
  reply.code(201)
  return { message: 'Created' }
})

fastify.get('/no-content', async (request, reply) => {
  reply.code(204).send()
})

fastify.get('/not-found', async (request, reply) => {
  reply.code(404)
  return { error: 'Not Found' }
})

4.3 设置响应头 #

javascript
fastify.get('/headers', async (request, reply) => {
  reply
    .header('X-Custom-Header', 'value')
    .header('Content-Type', 'application/json')
  return { message: 'Headers set' }
})

fastify.get('/cache', async (request, reply) => {
  reply
    .header('Cache-Control', 'public, max-age=3600')
    .header('ETag', 'abc123')
  return { data: 'cached data' }
})

4.4 Cookie操作 #

javascript
fastify.register(require('@fastify/cookie'))

fastify.get('/set-cookie', async (request, reply) => {
  reply.setCookie('sessionId', 'abc123', {
    httpOnly: true,
    secure: true,
    maxAge: 3600
  })
  return { message: 'Cookie set' }
})

fastify.get('/get-cookie', async (request, reply) => {
  const sessionId = request.cookies.sessionId
  return { sessionId }
})

4.5 重定向 #

javascript
fastify.get('/redirect', async (request, reply) => {
  reply.redirect('/target')
})

fastify.get('/redirect-external', async (request, reply) => {
  reply.redirect('https://example.com')
})

fastify.get('/redirect-301', async (request, reply) => {
  reply.redirect(301, '/new-location')
})

五、错误处理 #

5.1 抛出错误 #

javascript
fastify.get('/error', async (request, reply) => {
  throw fastify.httpErrors.notFound('User not found')
})

fastify.get('/error-custom', async (request, reply) => {
  const error = new Error('Something went wrong')
  error.statusCode = 500
  throw error
})

5.2 使用HTTP错误 #

javascript
fastify.register(require('@fastify/sensible'))

fastify.get('/not-found', async (request, reply) => {
  throw fastify.httpErrors.notFound()
})

fastify.get('/bad-request', async (request, reply) => {
  throw fastify.httpErrors.badRequest('Invalid parameters')
})

fastify.get('/unauthorized', async (request, reply) => {
  throw fastify.httpErrors.unauthorized('Token required')
})

fastify.get('/forbidden', async (request, reply) => {
  throw fastify.httpErrors.forbidden()
})

5.3 自定义错误处理 #

javascript
fastify.setErrorHandler((error, request, reply) => {
  if (error.validation) {
    reply.code(400).send({
      statusCode: 400,
      error: 'Validation Error',
      message: error.message
    })
    return
  }
  
  reply.code(error.statusCode || 500).send({
    statusCode: error.statusCode || 500,
    error: error.name,
    message: error.message
  })
})

六、异步处理 #

6.1 async/await #

javascript
fastify.get('/async', async (request, reply) => {
  const data = await fetchData()
  return data
})

async function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ data: 'fetched' })
    }, 100)
  })
}

6.2 Promise #

javascript
fastify.get('/promise', (request, reply) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ data: 'from promise' })
    }, 100)
  })
})

6.3 流式响应 #

javascript
fastify.get('/stream', (request, reply) => {
  const { Readable } = require('stream')
  
  const stream = new Readable({
    read() {}
  })
  
  stream.push('Hello ')
  stream.push('World!')
  stream.push(null)
  
  reply.type('text/plain').send(stream)
})

七、完整示例 #

7.1 用户API示例 #

javascript
const fastify = require('fastify')({ logger: true })

let users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' }
]

fastify.get('/users', async (request, reply) => {
  return users
})

fastify.get('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer' }
      }
    }
  }
}, async (request, reply) => {
  const user = users.find(u => u.id === request.params.id)
  if (!user) {
    throw fastify.httpErrors.notFound('User not found')
  }
  return user
})

fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      required: ['name', 'email'],
      properties: {
        name: { type: 'string' },
        email: { type: 'string', format: 'email' }
      }
    }
  }
}, async (request, reply) => {
  const newUser = {
    id: users.length + 1,
    ...request.body
  }
  users.push(newUser)
  reply.code(201)
  return newUser
})

fastify.put('/users/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'integer' }
      }
    },
    body: {
      type: 'object',
      properties: {
        name: { type: 'string' },
        email: { type: 'string', format: 'email' }
      }
    }
  }
}, async (request, reply) => {
  const index = users.findIndex(u => u.id === request.params.id)
  if (index === -1) {
    throw fastify.httpErrors.notFound('User not found')
  }
  users[index] = { ...users[index], ...request.body }
  return users[index]
})

fastify.delete('/users/:id', async (request, reply) => {
  const index = users.findIndex(u => u.id === request.params.id)
  if (index === -1) {
    throw fastify.httpErrors.notFound('User not found')
  }
  users.splice(index, 1)
  reply.code(204).send()
})

fastify.listen({ port: 3000 })

八、测试应用 #

8.1 使用curl测试 #

bash
curl http://localhost:3000/users
curl http://localhost:3000/users/1
curl -X POST -H "Content-Type: application/json" -d '{"name":"Charlie","email":"charlie@example.com"}' http://localhost:3000/users
curl -X PUT -H "Content-Type: application/json" -d '{"name":"Alice Updated"}' http://localhost:3000/users/1
curl -X DELETE http://localhost:3000/users/1

8.2 使用Node.js测试 #

javascript
const { test } = require('node:test')
const assert = require('node:assert')
const Fastify = require('fastify')
const app = require('./app')

test('GET /users', async (t) => {
  const fastify = Fastify()
  fastify.register(app)
  
  const response = await fastify.inject({
    method: 'GET',
    url: '/users'
  })
  
  assert.strictEqual(response.statusCode, 200)
  assert.ok(Array.isArray(JSON.parse(response.payload)))
})

九、总结 #

本章我们学习了:

  1. Hello World:最简单的Fastify应用
  2. 基础路由:GET、POST、PUT、DELETE等方法
  3. 请求处理:参数获取、验证
  4. 响应处理:JSON、状态码、响应头
  5. 错误处理:HTTP错误、自定义错误
  6. 异步处理:async/await、Promise、流

接下来让我们学习如何组织项目结构!

最后更新:2026-03-28