CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
Routes.md814 linesDownload Raw Back to Reference
1<h1 align="center">Fastify</h1>2 3## Routes4 5The route methods will configure the endpoints of your application. You have two6ways to declare a route with Fastify: the shorthand method and the full7declaration.8 9- [Full declaration](#full-declaration)10- [Routes options](#routes-options)11- [Shorthand declaration](#shorthand-declaration)12- [Url building](#url-building)13- [Async Await](#async-await)14- [Promise resolution](#promise-resolution)15- [Route Prefixing](#route-prefixing)16  - [Handling of / route inside prefixed17    plugins](#handling-of--route-inside-prefixed-plugins)18- [Custom Log Level](#custom-log-level)19- [Custom Log Serializer](#custom-log-serializer)20- [Config](#config)21- [Constraints](#constraints)22  - [Version Constraints](#version-constraints)23  - [Host Constraints](#host-constraints)24 25### Full declaration26<a id="full-declaration"></a>27 28```js29fastify.route(options)30```31 32### Routes options33<a id="options"></a>34 35* `method`: currently it supports `GET`, `HEAD`, `TRACE`, `DELETE`,36  `OPTIONS`, `PATCH`, `PUT` and `POST`. To accept more methods,37  the [`addHttpMethod`](./Server.md#addHttpMethod) must be used.38  It could also be an array of methods.39* `url`: the path of the URL to match this route (alias: `path`).40* `schema`: an object containing the schemas for the request and response. They41  need to be in [JSON Schema](https://json-schema.org/) format, check42  [here](./Validation-and-Serialization.md) for more info.43 44  * `body`: validates the body of the request if it is a POST, PUT, PATCH,45    TRACE, SEARCH, PROPFIND, PROPPATCH or LOCK method.46  * `querystring` or `query`: validates the querystring. This can be a complete47    JSON Schema object, with the property `type` of `object` and `properties`48    object of parameters, or simply the values of what would be contained in the49    `properties` object as shown below.50  * `params`: validates the params.51  * `response`: filter and generate a schema for the response, setting a schema52    allows us to have 10-20% more throughput.53* `exposeHeadRoute`: creates a sibling `HEAD` route for any `GET` routes.54  Defaults to the value of [`exposeHeadRoutes`](./Server.md#exposeHeadRoutes)55  instance option. If you want a custom `HEAD` handler without disabling this56  option, make sure to define it before the `GET` route.57* `attachValidation`: attach `validationError` to request, if there is a schema58  validation error, instead of sending the error to the error handler. The59  default [error format](https://ajv.js.org/api.html#error-objects) is the Ajv60  one.61* `onRequest(request, reply, done)`: a [function](./Hooks.md#onrequest) called62  as soon as a request is received, it could also be an array of functions.63* `preParsing(request, reply, done)`: a [function](./Hooks.md#preparsing) called64  before parsing the request, it could also be an array of functions.65* `preValidation(request, reply, done)`: a [function](./Hooks.md#prevalidation)66  called after the shared `preValidation` hooks, useful if you need to perform67  authentication at route level for example, it could also be an array of68  functions.69* `preHandler(request, reply, done)`: a [function](./Hooks.md#prehandler) called70  just before the request handler, it could also be an array of functions.71* `preSerialization(request, reply, payload, done)`: a72  [function](./Hooks.md#preserialization) called just before the serialization,73  it could also be an array of functions.74* `onSend(request, reply, payload, done)`: a [function](./Hooks.md#route-hooks)75  called right before a response is sent, it could also be an array of76  functions.77* `onResponse(request, reply, done)`: a [function](./Hooks.md#onresponse) called78  when a response has been sent, so you will not be able to send more data to79  the client. It could also be an array of functions.80* `onTimeout(request, reply, done)`: a [function](./Hooks.md#ontimeout) called81  when a request is timed out and the HTTP socket has been hung up.82* `onError(request, reply, error, done)`: a [function](./Hooks.md#onerror)83  called when an Error is thrown or sent to the client by the route handler.84* `handler(request, reply)`: the function that will handle this request. The85  [Fastify server](./Server.md) will be bound to `this` when the handler is86  called. Note: using an arrow function will break the binding of `this`.87* `errorHandler(error, request, reply)`: a custom error handler for the scope of88  the request. Overrides the default error global handler, and anything set by89  [`setErrorHandler`](./Server.md#seterrorhandler), for requests to the route.90  To access the default handler, you can access `instance.errorHandler`. Note91  that this will point to fastify's default `errorHandler` only if a plugin92  hasn't overridden it already.93* `childLoggerFactory(logger, binding, opts, rawReq)`: a custom factory function94  that will be called to produce a child logger instance for every request.95  See [`childLoggerFactory`](./Server.md#childloggerfactory) for more info.96  Overrides the default logger factory, and anything set by97  [`setChildLoggerFactory`](./Server.md#setchildloggerfactory), for requests to98  the route. To access the default factory, you can access99  `instance.childLoggerFactory`. Note that this will point to Fastify's default100  `childLoggerFactory` only if a plugin hasn't overridden it already.101* `validatorCompiler({ schema, method, url, httpPart })`: function that builds102  schemas for request validations. See the [Validation and103  Serialization](./Validation-and-Serialization.md#schema-validator)104  documentation.105* `serializerCompiler({ { schema, method, url, httpStatus, contentType } })`:106  function that builds schemas for response serialization. See the [Validation and107  Serialization](./Validation-and-Serialization.md#schema-serializer)108  documentation.109* `schemaErrorFormatter(errors, dataVar)`: function that formats the errors from110  the validation compiler. See the [Validation and111  Serialization](./Validation-and-Serialization.md#error-handling)112  documentation. Overrides the global schema error formatter handler, and113  anything set by `setSchemaErrorFormatter`, for requests to the route.114* `bodyLimit`: prevents the default JSON body parser from parsing request bodies115  larger than this number of bytes. Must be an integer. You may also set this116  option globally when first creating the Fastify instance with117  `fastify(options)`. Defaults to `1048576` (1 MiB).118* `logLevel`: set log level for this route. See below.119* `logSerializers`: set serializers to log for this route.120* `config`: object used to store custom configuration.121* `version`: a [semver](https://semver.org/) compatible string that defined the122  version of the endpoint. [Example](#version-constraints).123* `constraints`: defines route restrictions based on request properties or124  values, enabling customized matching using125  [find-my-way](https://github.com/delvedor/find-my-way) constraints. Includes126  built-in `version` and `host` constraints, with support for custom constraint127  strategies.128* `prefixTrailingSlash`: string used to determine how to handle passing `/` as a129  route with a prefix.130  * `both` (default): Will register both `/prefix` and `/prefix/`.131  * `slash`: Will register only `/prefix/`.132  * `no-slash`: Will register only `/prefix`.133 134  Note: this option does not override `ignoreTrailingSlash` in135  [Server](./Server.md) configuration.136 137* `request` is defined in [Request](./Request.md).138 139* `reply` is defined in [Reply](./Reply.md).140 141**Notice:** The documentation of `onRequest`, `preParsing`, `preValidation`,142`preHandler`, `preSerialization`, `onSend`, and `onResponse` are described in143more detail in [Hooks](./Hooks.md). Additionally, to send a response before the144request is handled by the `handler` please refer to [Respond to a request from a145hook](./Hooks.md#respond-to-a-request-from-a-hook).146 147Example:148```js149fastify.route({150  method: 'GET',151  url: '/',152  schema: {153    querystring: {154      type: 'object',155      properties: {156        name: { type: 'string' },157        excitement: { type: 'integer' }158      }159    },160    response: {161      200: {162        type: 'object',163        properties: {164          hello: { type: 'string' }165        }166      }167    }168  },169  handler: function (request, reply) {170    reply.send({ hello: 'world' })171  }172})173```174 175### Shorthand declaration176<a id="shorthand-declaration"></a>177 178The above route declaration is more *Hapi*-like, but if you prefer an179*Express/Restify* approach, we support it as well:180 181`fastify.get(path, [options], handler)`182 183`fastify.head(path, [options], handler)`184 185`fastify.post(path, [options], handler)`186 187`fastify.put(path, [options], handler)`188 189`fastify.delete(path, [options], handler)`190 191`fastify.options(path, [options], handler)`192 193`fastify.patch(path, [options], handler)`194 195Example:196```js197const opts = {198  schema: {199    response: {200      200: {201        type: 'object',202        properties: {203          hello: { type: 'string' }204        }205      }206    }207  }208}209fastify.get('/', opts, (request, reply) => {210  reply.send({ hello: 'world' })211})212```213 214`fastify.all(path, [options], handler)` will add the same handler to all the215supported methods.216 217The handler may also be supplied via the `options` object:218```js219const opts = {220  schema: {221    response: {222      200: {223        type: 'object',224        properties: {225          hello: { type: 'string' }226        }227      }228    }229  },230  handler: function (request, reply) {231    reply.send({ hello: 'world' })232  }233}234fastify.get('/', opts)235```236 237> Note: if the handler is specified in both the `options` and as the third238> parameter to the shortcut method then throws a duplicate `handler` error.239 240### Url building241<a id="url-building"></a>242 243Fastify supports both static and dynamic URLs.244 245To register a **parametric** path, use the *colon* before the parameter name.246For **wildcard**, use the *star*. *Remember that static routes are always247checked before parametric and wildcard.*248 249```js250// parametric251fastify.get('/example/:userId', function (request, reply) {252  // curl ${app-url}/example/12345253  // userId === '12345'254  const { userId } = request.params;255  // your code here256})257fastify.get('/example/:userId/:secretToken', function (request, reply) {258  // curl ${app-url}/example/12345/abc.zHi259  // userId === '12345'260  // secretToken === 'abc.zHi'261  const { userId, secretToken } = request.params;262  // your code here263})264 265// wildcard266fastify.get('/example/*', function (request, reply) {})267```268 269Regular expression routes are supported as well, but be aware that you have to270escape slashes. Take note that RegExp is also very expensive in terms of271performance!272```js273// parametric with regexp274fastify.get('/example/:file(^\\d+).png', function (request, reply) {275  // curl ${app-url}/example/12345.png276  // file === '12345'277  const { file } = request.params;278  // your code here279})280```281 282It is possible to define more than one parameter within the same couple of slash283("/"). Such as:284```js285fastify.get('/example/near/:lat-:lng/radius/:r', function (request, reply) {286  // curl ${app-url}/example/near/15°N-30°E/radius/20287  // lat === "15°N"288  // lng === "30°E"289  // r ==="20"290  const { lat, lng, r } = request.params;291  // your code here292})293```294*Remember in this case to use the dash ("-") as parameters separator.*295 296Finally, it is possible to have multiple parameters with RegExp:297```js298fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', function (request, reply) {299  // curl ${app-url}/example/at/08h24m300  // hour === "08"301  // minute === "24"302  const { hour, minute } = request.params;303  // your code here304})305```306In this case as parameter separator it is possible to use whatever character is307not matched by the regular expression.308 309The last parameter can be made optional if you add a question mark ("?") to the310end of the parameters name.311```js312fastify.get('/example/posts/:id?', function (request, reply) {313  const { id } = request.params;314  // your code here315})316```317In this case you can request `/example/posts` as well as `/example/posts/1`.318The optional param will be undefined if not specified.319 320Having a route with multiple parameters may negatively affect performance, so321prefer a single parameter approach whenever possible, especially on routes that322are on the hot path of your application. If you are interested in how we handle323the routing, check out [find-my-way](https://github.com/delvedor/find-my-way).324 325If you want a path containing a colon without declaring a parameter, use a326double colon. For example:327```js328fastify.post('/name::verb') // will be interpreted as /name:verb329```330 331### Async Await332<a id="async-await"></a>333 334Are you an `async/await` user? We have you covered!335```js336fastify.get('/', options, async function (request, reply) {337  const data = await getData()338  const processed = await processData(data)339  return processed340})341```342 343As you can see, we are not calling `reply.send` to send back the data to the344user. You just need to return the body and you are done!345 346If you need it you can also send back the data to the user with `reply.send`. In347this case do not forget to `return reply` or `await reply` in your `async`348handler or you will introduce a race condition in certain situations.349 350```js351fastify.get('/', options, async function (request, reply) {352  const data = await getData()353  const processed = await processData(data)354  return reply.send(processed)355})356```357 358If the route is wrapping a callback-based API that will call `reply.send()`359outside of the promise chain, it is possible to `await reply`:360 361```js362fastify.get('/', options, async function (request, reply) {363  setImmediate(() => {364    reply.send({ hello: 'world' })365  })366  await reply367})368```369 370Returning reply also works:371 372```js373fastify.get('/', options, async function (request, reply) {374  setImmediate(() => {375    reply.send({ hello: 'world' })376  })377  return reply378})379```380 381**Warning:**382* When using both `return value` and `reply.send(value)` at the same time, the383  first one that happens takes precedence, the second value will be discarded,384  and a *warn* log will also be emitted because you tried to send a response385  twice.386* Calling `reply.send()` outside of the promise is possible but requires special387  attention. For more details read [promise-resolution](#promise-resolution).388* You cannot return `undefined`. For more details read389  [promise-resolution](#promise-resolution).390 391### Promise resolution392<a id="promise-resolution"></a>393 394If your handler is an `async` function or returns a promise, you should be aware395of the special behavior that is necessary to support the callback and promise396control-flow. When the handler's promise is resolved, the reply will be397automatically sent with its value unless you explicitly await or return `reply`398in your handler.399 4001. If you want to use `async/await` or promises but respond with a value with401   `reply.send`:402    - **Do** `return reply` / `await reply`.403    - **Do not** forget to call `reply.send`.4042. If you want to use `async/await` or promises:405    - **Do not** use `reply.send`.406    - **Do** return the value that you want to send.407 408In this way, we can support both `callback-style` and `async-await`, with the409minimum trade-off. Despite so much freedom we highly recommend going with only410one style because error handling should be handled in a consistent way within411your application.412 413**Notice**: Every async function returns a promise by itself.414 415### Route Prefixing416<a id="route-prefixing"></a>417 418Sometimes you need to maintain two or more different versions of the same API; a419classic approach is to prefix all the routes with the API version number,420`/v1/user` for example. Fastify offers you a fast and smart way to create421different versions of the same API without changing all the route names by hand,422*route prefixing*. Let's see how it works:423 424```js425// server.js426const fastify = require('fastify')()427 428fastify.register(require('./routes/v1/users'), { prefix: '/v1' })429fastify.register(require('./routes/v2/users'), { prefix: '/v2' })430 431fastify.listen({ port: 3000 })432```433 434```js435// routes/v1/users.js436module.exports = function (fastify, opts, done) {437  fastify.get('/user', handler_v1)438  done()439}440```441 442```js443// routes/v2/users.js444module.exports = function (fastify, opts, done) {445  fastify.get('/user', handler_v2)446  done()447}448```449Fastify will not complain because you are using the same name for two different450routes, because at compilation time it will handle the prefix automatically451*(this also means that the performance will not be affected at all!)*.452 453Now your clients will have access to the following routes:454- `/v1/user`455- `/v2/user`456 457You can do this as many times as you want, it also works for nested `register`,458and route parameters are supported as well.459 460In case you want to use prefix for all of your routes, you can put them inside a461plugin:462 463```js464const fastify = require('fastify')()465 466const route = {467    method: 'POST',468    url: '/login',469    handler: () => {},470    schema: {},471}472 473fastify.register(function (app, _, done) {474  app.get('/users', () => {})475  app.route(route)476 477  done()478}, { prefix: '/v1' }) // global route prefix479 480await fastify.listen({ port: 3000 })481```482 483### Route Prefixing and fastify-plugin484<a id="fastify-plugin"></a>485 486Be aware that if you use487[`fastify-plugin`](https://github.com/fastify/fastify-plugin) for wrapping your488routes, this option will not work. You can still make it work by wrapping a489plugin in a plugin, e. g.:490```js491const fp = require('fastify-plugin')492const routes = require('./lib/routes')493 494module.exports = fp(async function (app, opts) {495  app.register(routes, {496    prefix: '/v1',497  })498}, {499  name: 'my-routes'500})501```502 503#### Handling of / route inside prefixed plugins504 505The `/` route has different behavior depending on if the prefix ends with `/` or506not. As an example, if we consider a prefix `/something/`, adding a `/` route507will only match `/something/`. If we consider a prefix `/something`, adding a508`/` route will match both `/something` and `/something/`.509 510See the `prefixTrailingSlash` route option above to change this behavior.511 512### Custom Log Level513<a id="custom-log-level"></a>514 515You might need different log levels in your routes; Fastify achieves this in a516very straightforward way.517 518You just need to pass the option `logLevel` to the plugin option or the route519option with the520[value](https://github.com/pinojs/pino/blob/master/docs/api.md#level-string)521that you need.522 523Be aware that if you set the `logLevel` at plugin level, also the524[`setNotFoundHandler`](./Server.md#setnotfoundhandler) and525[`setErrorHandler`](./Server.md#seterrorhandler) will be affected.526 527```js528// server.js529const fastify = require('fastify')({ logger: true })530 531fastify.register(require('./routes/user'), { logLevel: 'warn' })532fastify.register(require('./routes/events'), { logLevel: 'debug' })533 534fastify.listen({ port: 3000 })535```536 537Or you can directly pass it to a route:538```js539fastify.get('/', { logLevel: 'warn' }, (request, reply) => {540  reply.send({ hello: 'world' })541})542```543*Remember that the custom log level is applied only to the routes, and not to544the global Fastify Logger, accessible with `fastify.log`*545 546### Custom Log Serializer547<a id="custom-log-serializer"></a>548 549In some contexts, you may need to log a large object but it could be a waste of550resources for some routes. In this case, you can define custom551[`serializers`](https://github.com/pinojs/pino/blob/master/docs/api.md#serializers-object)552and attach them in the right context!553 554```js555const fastify = require('fastify')({ logger: true })556 557fastify.register(require('./routes/user'), {558  logSerializers: {559    user: (value) => `My serializer one - ${value.name}`560  }561})562fastify.register(require('./routes/events'), {563  logSerializers: {564    user: (value) => `My serializer two - ${value.name} ${value.surname}`565  }566})567 568fastify.listen({ port: 3000 })569```570 571You can inherit serializers by context:572 573```js574const fastify = Fastify({575  logger: {576    level: 'info',577    serializers: {578      user (req) {579        return {580          method: req.method,581          url: req.url,582          headers: req.headers,583          host: req.host,584          remoteAddress: req.ip,585          remotePort: req.socket.remotePort586        }587      }588    }589  }590})591 592fastify.register(context1, {593  logSerializers: {594    user: value => `My serializer father - ${value}`595  }596})597 598async function context1 (fastify, opts) {599  fastify.get('/', (req, reply) => {600    req.log.info({ user: 'call father serializer', key: 'another key' })601    // shows: { user: 'My serializer father - call father  serializer', key: 'another key' }602    reply.send({})603  })604}605 606fastify.listen({ port: 3000 })607```608 609### Config610<a id="routes-config"></a>611 612Registering a new handler, you can pass a configuration object to it and613retrieve it in the handler.614 615```js616// server.js617const fastify = require('fastify')()618 619function handler (req, reply) {620  reply.send(reply.routeOptions.config.output)621}622 623fastify.get('/en', { config: { output: 'hello world!' } }, handler)624fastify.get('/it', { config: { output: 'ciao mondo!' } }, handler)625 626fastify.listen({ port: 3000 })627```628 629### Constraints630<a id="constraints"></a>631 632Fastify supports constraining routes to match only certain requests based on633some property of the request, like the `Host` header, or any other value via634[`find-my-way`](https://github.com/delvedor/find-my-way) constraints.635Constraints are specified in the `constraints` property of the route options.636Fastify has two built-in constraints ready for use: the `version` constraint and637the `host` constraint, and you can add your own custom constraint strategies to638inspect other parts of a request to decide if a route should be executed for a639request.640 641#### Version Constraints642 643You can provide a `version` key in the `constraints` option to a route.644Versioned routes allow you to declare multiple handlers for the same HTTP route645path, which will then be matched according to each request's `Accept-Version`646header. The `Accept-Version` header value should follow the647[semver](https://semver.org/) specification, and routes should be declared with648exact semver versions for matching.649 650Fastify will require a request `Accept-Version` header to be set if the route651has a version set, and will prefer a versioned route to a non-versioned route652for the same path. Advanced version ranges and pre-releases currently are not653supported.654 655*Be aware that using this feature will cause a degradation of the overall656performances of the router.*657 658```js659fastify.route({660  method: 'GET',661  url: '/',662  constraints: { version: '1.2.0' },663  handler: function (request, reply) {664    reply.send({ hello: 'world' })665  }666})667 668fastify.inject({669  method: 'GET',670  url: '/',671  headers: {672    'Accept-Version': '1.x' // it could also be '1.2.0' or '1.2.x'673  }674}, (err, res) => {675  // { hello: 'world' }676})677```678 679> ## ⚠  Security Notice680> Remember to set a681> [`Vary`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary)682> header in your responses with the value you are using for defining the683> versioning (e.g.: `'Accept-Version'`), to prevent cache poisoning attacks. You684> can also configure this as part of your Proxy/CDN.685>686> ```js687> const append = require('vary').append688> fastify.addHook('onSend', (req, reply, payload, done) => {689>   if (req.headers['accept-version']) { // or the custom header you are using690>     let value = reply.getHeader('Vary') || ''691>     const header = Array.isArray(value) ? value.join(', ') : String(value)692>     if ((value = append(header, 'Accept-Version'))) { // or the custom header you are using693>       reply.header('Vary', value)694>     }695>   }696>  done()697> })698> ```699 700If you declare multiple versions with the same major or minor, Fastify will701always choose the highest compatible with the `Accept-Version` header value.702 703If the request will not have the `Accept-Version` header, a 404 error will be704returned.705 706It is possible to define a custom version matching logic. This can be done707through the [`constraints`](./Server.md#constraints) configuration when creating708a Fastify server instance.709 710#### Host Constraints711 712You can provide a `host` key in the `constraints` route option for to limit that713route to only be matched for certain values of the request `Host` header. `host`714constraint values can be specified as strings for exact matches or RegExps for715arbitrary host matching.716 717```js718fastify.route({719  method: 'GET',720  url: '/',721  constraints: { host: 'auth.fastify.dev' },722  handler: function (request, reply) {723    reply.send('hello world from auth.fastify.dev')724  }725})726 727fastify.inject({728  method: 'GET',729  url: '/',730  headers: {731    'Host': 'example.com'732  }733}, (err, res) => {734  // 404 because the host doesn't match the constraint735})736 737fastify.inject({738  method: 'GET',739  url: '/',740  headers: {741    'Host': 'auth.fastify.dev'742  }743}, (err, res) => {744  // => 'hello world from auth.fastify.dev'745})746```747 748RegExp `host` constraints can also be specified allowing constraining to hosts749matching wildcard subdomains (or any other pattern):750 751```js752fastify.route({753  method: 'GET',754  url: '/',755  constraints: { host: /.*\.fastify\.dev/ }, // will match any subdomain of fastify.dev756  handler: function (request, reply) {757    reply.send('hello world from ' + request.headers.host)758  }759})760```761 762#### Asynchronous Custom Constraints763 764Custom constraints can be provided and the `constraint` criteria can be765fetched from another source such as `database`. The use of asynchronous766custom constraints should be a last resort as it impacts router767performance.768 769```js770function databaseOperation(field, done) {771  done(null, field)772}773 774const secret = {775  // strategy name for referencing in the route handler `constraints` options776  name: 'secret',777  // storage factory for storing routes in the find-my-way route tree778  storage: function () {779    let handlers = {}780    return {781      get: (type) => { return handlers[type] || null },782      set: (type, store) => { handlers[type] = store }783    }784  },785  // function to get the value of the constraint from each incoming request786  deriveConstraint: (req, ctx, done) => {787    databaseOperation(req.headers['secret'], done)788  },789  // optional flag marking if handlers without constraints can match requests that have a value for this constraint790  mustMatchWhenDerived: true791}792```793 794> ## ⚠  Security Notice795> When using with asynchronous constraint. It is highly recommend never return error796> inside the callback. If the error is not preventable, it is recommended to provide797> a custom `frameworkErrors` handler to deal with it. Otherwise, you route selection798> may break or expose sensitive information to attackers.799>800> ```js801> const Fastify = require('fastify')802>803> const fastify = Fastify({804>   frameworkErrors: function (err, res, res) {805>     if (err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) {806>       res.code(400)807>       return res.send("Invalid header provided")808>     } else {809>       res.send(err)810>     }811>   }812> })813> ```814