{"id":20258737,"url":"https://github.com/47ng/fastify-micro","last_synced_at":"2025-04-11T01:12:02.318Z","repository":{"id":36965326,"uuid":"235956728","full_name":"47ng/fastify-micro","owner":"47ng","description":"Opinionated Node.js microservices framework built on Fastify ⚡️","archived":false,"fork":false,"pushed_at":"2024-05-07T20:21:16.000Z","size":1682,"stargazers_count":44,"open_issues_count":18,"forks_count":4,"subscribers_count":3,"default_branch":"next","last_synced_at":"2025-04-11T01:11:53.567Z","etag":null,"topics":["fastify","microservice-framework","sentry"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/47ng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["franky47"],"liberapay":"francoisbest","custom":["https://paypal.me/francoisbest?locale.x=fr_FR"]}},"created_at":"2020-01-24T07:17:34.000Z","updated_at":"2025-03-30T00:46:25.000Z","dependencies_parsed_at":"2024-06-20T00:15:22.459Z","dependency_job_id":"d859ca36-e56c-43a6-8985-88da3a8dfb9e","html_url":"https://github.com/47ng/fastify-micro","commit_stats":{"total_commits":319,"total_committers":5,"mean_commits":63.8,"dds":0.3134796238244514,"last_synced_commit":"4d40d6b140c2f0da8b6f8f574cbdb0174a9c07fc"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":"47ng/typescript-library-starter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/47ng%2Ffastify-micro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/47ng%2Ffastify-micro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/47ng%2Ffastify-micro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/47ng%2Ffastify-micro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/47ng","download_url":"https://codeload.github.com/47ng/fastify-micro/tar.gz/refs/heads/next","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248322571,"owners_count":21084337,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fastify","microservice-framework","sentry"],"created_at":"2024-11-14T11:11:01.909Z","updated_at":"2025-04-11T01:12:02.286Z","avatar_url":"https://github.com/47ng.png","language":"TypeScript","funding_links":["https://github.com/sponsors/franky47","https://liberapay.com/francoisbest","https://paypal.me/francoisbest?locale.x=fr_FR"],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\u003ccode\u003efastify-micro\u003c/code\u003e\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![NPM](https://img.shields.io/npm/v/fastify-micro?color=red)](https://www.npmjs.com/package/fastify-micro)\n[![MIT License](https://img.shields.io/github/license/47ng/fastify-micro.svg?color=blue)](https://github.com/47ng/fastify-micro/blob/master/LICENSE)\n[![Continuous Integration](https://github.com/47ng/fastify-micro/workflows/Continuous%20Integration/badge.svg?branch=next)](https://github.com/47ng/fastify-micro/actions)\n[![Coverage Status](https://coveralls.io/repos/github/47ng/fastify-micro/badge.svg?branch=next)](https://coveralls.io/github/47ng/fastify-micro?branch=next)\n\n\u003c/div\u003e\n\n\u003cp align=\"center\"\u003e\n  Opinionated Node.js microservices framework built on \u003ca href=\"https://fastify.io\"\u003eFastify\u003c/a\u003e.\n\u003c/p\u003e\n\n## Features\n\n- Secure and useful logging\n- [Auto-load](https://github.com/fastify/fastify-autoload) routes \u0026 plugins from the filesystem _(opt-in)_\n- Built-in [Sentry](#sentry) support for error reporting _(opt-in)_\n- Service health monitoring\n- Graceful exit\n- First class TypeScript support\n\n## Installation\n\n```shell\n$ yarn add fastify-micro\n# or\n$ npm i fastify-micro\n```\n\n## Usage\n\nMinimal example:\n\n```ts\nimport { createServer, startServer } from 'fastify-micro'\n\nconst server = createServer()\n\nstartServer(server)\n```\n\n## Documentation\n\n### Environment Variables\n\nDetails of the required and accepted (optional) environment variables\nare available in the [`.env.example`](./.env.example) file.\n\n### Listening port\n\nYou can provide the port number where the server will be listening as\nthe second argument of `startServer`:\n\n```ts\nimport { createServer, startServer } from 'fastify-micro'\n\nconst server = createServer()\nstartServer(server, 3000)\n```\n\nIf omitted, the port number will be read from the `PORT` environment\nvariable:\n\n```ts\n// process.env.PORT = 4000\n\nimport { createServer, startServer } from 'fastify-micro'\n\nconst server = createServer()\nstartServer(server)\n\n// Server started on 0.0.0.0:4000\n```\n\nIf no value is specified either via code or environment, the default port will\nbe 3000.\n\n### Auto-loading plugins and routes from the filesystem\n\nPlugins and routes can be loaded from the filesystem using\n[`fastify-autoload`](https://github.com/fastify/fastify-autoload):\n\n```ts\nimport path from 'path'\nimport { createServer } from 'fastify-micro'\n\ncreateServer({\n  plugins: {\n    dir: path.join(__dirname, 'plugins')\n  },\n  routes: {\n    dir: path.join(__dirname, 'routes')\n  }\n})\n```\n\nThe `plugins` and `routes` options are `fastify-autoload` configuration objects.\n\nAs recommended by Fastify, plugins will be loaded first, then routes.\nAttach your external services, decorators and hooks as plugin files, so that\nthey will be loaded when declaring your routes.\n\n#### Printing Routes\n\nIn development, the server will log the route tree on startup.\nThis can be configured:\n\n```ts\ncreateServer({\n  printRoutes:\n    | 'auto'    // default: `console` in development, silent in production.\n    | 'console' // always pretty-print routes using `console.info` (for humans)\n    | 'logger'  // always print as NDJSON as part of the app log stream (info level)\n    | false     // disable route printing\n})\n```\n\n### Other default plugins\n\nThe following plugins are loaded by default:\n\n- [`fastify-sensible`](https://github.com/fastify/fastify-sensible),\n  for convention-based error handling.\n\n### Loading other plugins\n\nThe server returned by `createServer` is a Fastify instance, you can\nregister any Fastify-compatible plugin onto it, and use the full Fastify\nAPI:\n\n```ts\nconst server = createServer()\n\nserver.register(require('fastify-cors'))\n\nserver.get('/', () =\u003e 'Hello, world !')\n```\n\n### Logging\n\nFastify already has a great logging story with\n[pino](https://github.com/pinojs/pino), this builds upon it.\n\n#### Logs Redaction\n\nLogs should be safe: no accidental leaking of access tokens and other\nsecrets through environment variables being logged. For this,\n[`redact-env`](https://github.com/47ng/redact-env) is used.\n\nBy default, it will only redact the value of `SENTRY_DSN` (see\n[Sentry](#sentry) for more details), but you can pass it additional\nenvironment variables to redact:\n\n```ts\ncreateServer({\n  // The values of these environment variables\n  // will be redacted in the logs:\n  redactEnv: [\n    'JWT_SECRET',\n    'AWS_S3_TOKEN',\n    'DATABASE_URI'\n    // etc...\n  ]\n})\n```\n\nYou can also redact log fields by passing [Pino redact paths](https://getpino.io/#/docs/redaction)\nto the `redactLogPaths` option:\n\n```ts\ncreateServer({\n  // The values of these headers\n  // will be redacted in the logs:\n  redactLogPaths: [\n    'req.headers[\"x-myapp-client-secret\"]',\n    'res.headers[\"x-myapp-server-secret\"]'\n    // etc...\n  ]\n})\n```\n\nThe following security headers will be redacted by default:\n\n- Request headers:\n  - `Cookie`\n  - `Authorization`\n  - `X-Secret-Token`\n  - `X-CSRF-Token`\n- Response headers:\n  - `Set-Cookie`\n\n#### Environment Context\n\nIn case you want to perform log aggregation across your services, it can\nbe useful to know who generated a log entry.\n\nFor that, you can pass a `name` in the options. It will add a `from`\nfield in the logs with that name:\n\n```ts\nconst server = createServer({\n  name: 'api'\n})\n\n// The `name` property is now available on your server:\nserver.log.info({ msg: `Hello, ${server.name}` })\n// {\"from\":\"api\":\"msg\":\"Hello, api\",...}\n```\n\nTo add more context to your logs, you can set the following optional\nenvironment variables:\n\n| Env Var Name  | Log Key    | Description                                              |\n| ------------- | ---------- | -------------------------------------------------------- |\n| `INSTANCE_ID` | `instance` | An identifier for the machine that runs your application |\n| `COMMIT_ID`   | `commit`   | The git SHA-1 of your code                               |\n\n\u003e _**Note**_: for both `INSTANCE_ID` and `COMMIT_ID`, only the first 8\n\u003e characters will be logged or sent to Sentry.\n\n#### Request ID\n\nBy default, Fastify uses an incremental integer for its request ID, which\nis fast but lacks context and immediate visual identification.\n\nInstead, `fastify-micro` uses a request ID that looks like this:\n\n```\nTo9hgCK4MvOmFRVM.oPoAOhj93kEgbIdV\n```\n\nIt is made of two parts, separated by a dot `'.'`:\n\n- `To9hgCK4MvOmFRVM` is the user fingerprint\n- `oPoAOhj93kEgbIdV` is a random identifier\n\nThe user fingerprint is a hash of the following elements:\n\n- The source IP address\n- The user-agent header\n- A salt used for anonymization\n\nThe second part of the request ID is a random string of base64 characters\nthat will change for every request, but stay common across the lifetime\nof the request, making it easier to visualize which requests are linked\nin the logs:\n\n```json\n// Other log fields removed for brievity\n{\"reqId\":\"To9hgCK4MvOmFRVM.psM5GNErJq4l6OD6\",\"req\":{\"method\":\"GET\",\"url\":\"/foo\"}}\n{\"reqId\":\"To9hgCK4MvOmFRVM.psM5GNErJq4l6OD6\",\"res\":{\"statusCode\":200}}\n{\"reqId\":\"To9hgCK4MvOmFRVM.oPoAOhj93kEgbIdV\",\"req\":{\"method\":\"POST\",\"url\":\"/bar\"}}\n{\"reqId\":\"To9hgCK4MvOmFRVM.oPoAOhj93kEgbIdV\",\"res\":{\"statusCode\":201}}\n{\"reqId\":\"KyGsnkFDdtKLQUaW.Jj6TgkSAYJ4hcxLR\",\"req\":{\"method\":\"GET\",\"url\":\"/egg\"}}\n{\"reqId\":\"KyGsnkFDdtKLQUaW.Jj6TgkSAYJ4hcxLR\",\"res\":{\"statusCode\":200}}\n```\n\nHere we can quickly see that:\n\n- There are two users interacting with the service\n- User `To9hgCK4MvOmFRVM` made two requests:\n  - `psM5GNErJq4l6OD6` - `GET /foo -\u003e 200`\n  - `oPoAOhj93kEgbIdV` - `POST /bar -\u003e 201`\n- User `KyGsnkFDdtKLQUaW` made one request:\n  - `Jj6TgkSAYJ4hcxLR` - `GET /egg -\u003e 200`\n\n#### Anonymising Request ID Fingerprints\n\nBy default, request ID fingerprints are rotated every time an app is\nbuilt (when `createServer` is called). This will most likely correspond\nto when your app starts, and would make it impossible to track users\nacross restarts of your app, or across multiple instances when scaling\nup. While it's good for privacy (while keeping a good debugging value\nper-service), it will be a pain for distributed systems.\n\nIf you need reproducibility in the fingerprint, you can set the\n`LOG_FINGERPRINT_SALT` environment variable to a constant across your\nservices / instances.\n\n### Sentry\n\nBuilt-in support for [Sentry](https://sentry.io) is provided, and can be\nactivated by setting the `SENTRY_DSN` environment variable to the\n[DSN](https://docs.sentry.io/error-reporting/quickstart/?platform=node#configure-the-sdk)\nthat is found in your project settings.\n\nSentry will receive any unhandled errors (5xx) thrown by your\napplication. 4xx errors are considered \"handled\" errors and will not be\nreported.\n\nYou can manually report an error, at the server or request level:\n\n```ts\n// Anywhere you have access to the server object:\nconst error = new Error('Manual error report')\nserver.sentry.report(error)\n\n// In a route:\nconst exampleRoute = (req, res) =\u003e {\n  const error = new Error('Error from a route')\n  // This will add request context to the error:\n  req.sentry.report(error)\n}\n```\n\n#### Enriching error reports\n\nYou can enrich your error reports by defining two async callbacks:\n\n- `getUser`, to retrieve user information to pass to Sentry\n- `getExtra`, to add two kinds of extra key:value information:\n  - `tags`: tags are searchable string-based key/value pairs, useful for filtering issues/events.\n  - `context`: extra data to display in issues/events, not searchable.\n\nExample:\n\n```ts\nimport { createServer } from 'fastify-micro'\n\ncreateServer({\n  sentry: {\n    getUser: async (server, req) =\u003e {\n      // Example: fetch user from database\n      const user = await server.db.findUser(req.auth.userID)\n      return user\n    },\n    getExtra: async (server, req) =\u003e {\n      // Req may be undefined here\n      return {\n        tags: {\n          foo: 'bar' // Can search/filter issues by `foo`\n        },\n        context: {\n          egg: 'spam'\n        }\n      }\n    }\n  }\n})\n```\n\n\u003e _**ProTip**_: if you're returning Personally Identifiable Information\n\u003e in your enrichment callbacks, don't forget to mention it in your\n\u003e privacy policy 🙂\n\nYou can also enrich manually-reported errors:\n\n```ts\nconst exampleRoute = (req, res) =\u003e {\n  const error = new Error('Error from a route')\n  // Add extra data to the error\n  req.sentry.report(error, {\n    tags: {\n      projectID: req.params.projectID\n    },\n    context: {\n      performance: 42\n    }\n  })\n}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\n  \u003ch4\u003eNote: v2 to v3 migration\u003c/h4\u003e\n\u003c/summary\u003e\n\nin versions \u003c= 2.x.x, the request object was passed as the second argument to the `report` function.\n\nTo migrate to version 3.x.x, you can remove this argument and use the `sentry`\ndecoration on the request instead:\n\n```ts\nconst exampleRoute = (req, res) =\u003e {\n  const error = new Error('Error from a route')\n\n  // version 2.x.x\n  server.sentry.report(error, req, {\n    // Extra context\n  })\n\n  // version 3.x.x\n  req.sentry.report(error, {\n    // Extra context\n  })\n}\n```\n\n\u003c/details\u003e\n\n#### Sentry Releases\n\nThere are two ways to tell Sentry about which\n[Release](https://docs.sentry.io/workflow/releases/?platform=node)\nto use when reporting errors:\n\n- Via the `SENTRY_RELEASE` environment variable\n- Via the options:\n\n```ts\nimport { createServer } from 'fastify-micro'\n\ncreateServer({\n  sentry: {\n    release: 'foo'\n  }\n})\n```\n\nA value passed in the options will take precedence over a value passed\nby the environment variable.\n\n### Graceful exit\n\nWhen receiving `SIGINT` or `SIGTERM`, Fastify applications quit instantly,\npotentially leaking file descriptors or open resources.\n\nTo clean up before exiting, add a `cleanupOnExit` callback in the options:\n\n```ts\ncreateServer({\n  cleanupOnExit: async app =\u003e {\n    // Release external resources\n    await app.database.close()\n  }\n})\n```\n\nThis uses the Fastify `onClose` hook, which will be called when receiving a\ntermination signal. If the onClose hooks take too long to resolve, the process\nwill perform a hard-exit after a timeout.\n\nYou can specify the list of signals to handle gracefully, along with a few other\noptions:\n\n```ts\ncreateServer({\n  gracefulShutdown: {\n    signals: ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGTSTP'],\n\n    // How long to wait for the onClose hooks to resolve\n    // before perfoming a hard-exit of the process (default 10s):\n    timeoutMs: 20_000,\n\n    // The exit code to use when hard-exiting (default 1)\n    hardExitCode: 123\n  }\n})\n```\n\n### Service availability monitoring \u0026 health check\n\n[`under-pressure`](https://github.com/fastify/under-pressure)\nis used to monitor the health of the service, and expose a health check\nroute at `/_health`.\n\nDefault configuration:\n\n- Max event loop delay: 1 second\n- Health check interval: 5 seconds\n\nOptions for `under-pressure` can be provided under the `underPressure`\nkey in the server options:\n\n```ts\ncreateServer({\n  underPressure: {\n    // Custom health check for testing attached services' health:\n    healthCheck: async server =\u003e {\n      try {\n        const databaseOk = Boolean(await server.db.checkConnection())\n        // Returned data will show up in the endpoint's response:\n        return {\n          databaseOk\n        }\n      } catch (error) {\n        server.sentry.report(error)\n        return false\n      }\n    },\n\n    // You can also pass anything accepted by under-pressure options:\n    maxEventLoopDelay: 3000\n  }\n})\n```\n\nIf for some reason you wish to disable service health monitoring, you can set\nthe `FASTIFY_MICRO_DISABLE_SERVICE_HEALTH_MONITORING` environment variable to `true`.\n\n## Deprecated APIs\n\n- `configure` _(will be removed in v4.x)_: Use `plugins` with full `fastify-autoload` options.\n- `routesDir` _(will be removed in v4.x)_: Use `routes` with full `fastify-autoload` options.\n\n## License\n\n[MIT](https://github.com/47ng/fastify-micro/blob/master/LICENSE) - Made with ❤️ by [François Best](https://francoisbest.com)\n\nUsing this package at work ? [Sponsor me](https://github.com/sponsors/franky47) to help with support and maintenance.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F47ng%2Ffastify-micro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F47ng%2Ffastify-micro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F47ng%2Ffastify-micro/lists"}