{"id":27934213,"url":"https://github.com/jaredwray/fastify-fusion","last_synced_at":"2026-03-08T23:08:11.641Z","repository":{"id":291320973,"uuid":"977269297","full_name":"jaredwray/fastify-fusion","owner":"jaredwray","description":"Fastify API framework fused together with best practices","archived":false,"fork":false,"pushed_at":"2025-08-30T16:57:53.000Z","size":1085,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-30T18:31:02.504Z","etag":null,"topics":["api","fastify","nodejs","swagger"],"latest_commit_sha":null,"homepage":"https://fastify-fusion.org","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/jaredwray.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-05-03T20:06:48.000Z","updated_at":"2025-08-30T16:57:50.000Z","dependencies_parsed_at":"2025-06-18T20:27:44.746Z","dependency_job_id":"4a060d78-372b-4997-84b1-cbbfec7ae3fc","html_url":"https://github.com/jaredwray/fastify-fusion","commit_stats":null,"previous_names":["jaredwray/fastify-fusion"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/jaredwray/fastify-fusion","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredwray%2Ffastify-fusion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredwray%2Ffastify-fusion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredwray%2Ffastify-fusion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredwray%2Ffastify-fusion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaredwray","download_url":"https://codeload.github.com/jaredwray/fastify-fusion/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredwray%2Ffastify-fusion/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273075799,"owners_count":25041257,"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","status":"online","status_checked_at":"2025-09-01T02:00:09.058Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["api","fastify","nodejs","swagger"],"created_at":"2025-05-07T05:27:46.948Z","updated_at":"2026-03-08T23:08:11.634Z","avatar_url":"https://github.com/jaredwray.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Fastify Fusion](https://jaredwray.com/images/fastify-fusion.svg)\n\n# fastify-fusion\n[![codecov](https://codecov.io/gh/jaredwray/fastify-fusion/graph/badge.svg?token=ieUorXA15v)](https://codecov.io/gh/jaredwray/fastify-fusion)\n[![tests](https://github.com/jaredwray/fastify-fusion/actions/workflows/tests.yaml/badge.svg)](https://github.com/jaredwray/fastify-fusion/actions/workflows/tests.yaml)\n[![npm](https://img.shields.io/npm/v/fastify-fusion)](https://www.npmjs.com/package/fastify-fusion)\n[![npm](https://img.shields.io/npm/dm/fastify-fusion)](https://www.npmjs.com/package/fastify-fusion)\n[![license](https://img.shields.io/github/license/jaredwray/fastify-fusion)](https://github.com/jaredwray/fastify-fusion/blob/main/LICENSE)\n\nFastify API framework with `best practices` and `plugins` fused together to make it easy to build and maintain your API.\n\n# Features\n- **Fuse** - Easily create a Fastify app with sensible defaults via `fuse()`.\n- **Start** - Easy to start your Fastify app with sensible defaults via `start()`.\n- **Cache** - High-performance layer 1/2 caching with the `cacheable` library integrated as `app.cache`.\n- **OpenAPI** - OpenAPI docs generated using `fastify-swagger` and `scalar` with sensible defaults.\n- **Helmet** - Security headers set using `@fastify/helmet` with sensible defaults.\n- **CORS** - CORS enabled using `@fastify/cors` with sensible defaults for apis.\n- **Logging** - Pino Configured using `pino-pretty` to make it easy to read and access to a `logger` instance.\n- **Rate Limiting** - Rate limiting using `@fastify/rate-limit` with sensible defaults.\n- **Static Paths**: Default `./public` static path and easy to add / configure your own.\n- **Regularly updated**: Updated regularly to keep up with the latest Fastify and TypeScript features.\n\n# Table of Contents\n- [Installation](#installation)\n- [Usage](#usage)\n- [Fuse Options](#fuse-options)\n- [Fastify Start](#fastify-start)\n- [Cache](#cache)\n- [Static Paths](#static-paths)\n- [Logging](#logging)\n- [Helmet](#helmet)\n- [Rate Limiting](#rate-limiting)\n- [CORS](#cors)\n- [Open API and Docs UX](#open-api-and-docs-ux)\n- [How to Contribute](#how-to-contribute)\n- [Licensing and Copyright](#licensing-and-copyright)\n\n# Installation\n```bash\nnpm install fastify-fusion fastify\n```\n\n# Usage\n\nIf you already have a Fastify app, you can use `fuse` to add the default options and plugins to your app.\n\n```typescript\nimport fastify from 'fastify';\nimport { fuse, FuseOptions } from 'fastify-fusion';\n\nconst app = fastify();\n\n// Fuse the app. It will use the default options if none are provided. If you want to use your own options, pass them in as the second argument.\nawait fuse(app);\n```\n\nYou can also pass in the `FuseOptions` to customize your fastify instance.\n\n```typescript\nimport fastify from 'fastify';\nimport { fuse, FuseOptions } from 'fastify-fusion';\n\nconst fuseOptions: FuseOptions = {\n  static: true,\n  log: true,\n  helmet: false,\n  rateLimit: false,\n  cache: true\n};\n\nconst app = await fuse(app, fuseOptions);\n```\n\nYou can also use the built in `start()` function to get up and running quickly. This function will create a Fastify app and start the server for you.\n\n```typescript\nimport fastify from 'fastify';\nimport { start, fuse } from 'fastify-fusion';\n\nconst app = fastify();\n\n// fuse the app with default options\nawait fuse(app);\n\n// start the app. Set the options with StartOptions type.\nawait start(app);\n```\n\n# Fuse Options\n\nYou can customize the behavior of `fastify-fusion` by passing in options to the `fuse` function or when creating a new Fastify app with `fastify()`.\n\n```typescript\nimport { fuse, FuseOptions } from 'fastify-fusion';\nimport Fastify from 'fastify';\nconst app = Fastify();\nconst options: FuseOptions = {\n  helmet: {\n    contentSecurityPolicy: false, // Disable CSP for simplicity\n  },\n  static: {\n    path: '/static/', // Serve static files from /public\n    dir: './static', // Path to the static files\n  },\n  rateLimit: {\n\tmax: 200, // Allow 200 requests per minute per IP address\n  },\n};\nawait fuse(app, options);\n```\n\nHere is the `FuseOptions` interface with all the available options:\n\n```typescript\nexport type FuseOptions = {\n\tstatic?: boolean | StaticOptions;\n\tlog?: boolean | LoggerOptions;\n\thelmet?: boolean | FastifyHelmetOptions;\n\trateLimit?: boolean | FastifyRateLimitOptions;\n\tcors?: boolean | FastifyCorsOptions;\n\topenApi?: boolean | OpenApiOptions;\n\tcache?: boolean | CacheableOptions;\n};\n```\n\nBy default, all the options are set to `true`, which means that all of the default settings will be applied. You can learn about the default settings in each features's documentation below.\n\n# Fastify Start\n\nYou can start your Fastify app using the `start` function. This function will start the Fastify server and log the URL where the server is running. This will use the default configuration for the server, which includes a default port of `3000` and a host of `0.0.0.0` if `process.env.PORT` or `process.env.HOST` are not set. \n\n```typescript\nimport { fastify, start } from 'fastify-fusion';\n\nconst app = await fastify();\nstart(app);\n```\n\nYou can customize the port and host by passing in a `StartOptions` object to the `start` function.\n\n```typescript\nimport { fastify, start, type StartOptions } from 'fastify-fusion';\n\nconst app = await fastify();\nconst options: StartOptions = {\n  port: 3001, // Set the port to 3000\n  host: '127.0.0.1', // Set the host to 127.0.0.1\n};\n\nstart(app, options);\n```\n\nIf you want to also set the startup message when the server starts, you can pass in a `message` function to the `StartOptions`. This function will receive the host and port as arguments and should return a string that will be logged to the console.\n\n```typescript\nimport { fastify, start, type StartOptions } from 'fastify-fusion';\nconst app = await fastify();\nconst options: StartOptions = {\n  message: (host, port) =\u003e `🌏 started successfully at http://${host}:${port}`,\n};\nstart(app, options);\n```\n\n# Cache\n\n`fastify-fusion` integrates the high-performance `cacheable` library as `app.cache`, providing layer 1/2 caching capabilities with advanced features like statistics, TTL management, and non-blocking operations.\n\n## Default Configuration\n\n```typescript\nexport const defaultCacheableOptions: CacheableOptions = {\n\tttl: \"1h\", // Default 1 hour TTL\n\tstats: true, // Enable statistics by default\n\tnonBlocking: true, // Non-blocking secondary operations\n};\n```\n\n## Basic Usage\n\nOnce fused, the cache is available on your Fastify instance as `app.cache`:\n\n```typescript\nimport fastify from 'fastify';\nimport { fuse } from 'fastify-fusion';\n\nconst app = fastify();\nawait fuse(app);\n\n// Use cache in routes\napp.get('/user/:id', async (request, reply) =\u003e {\n  const userId = request.params.id;\n  const cacheKey = `user:${userId}`;\n  \n  // Try to get from cache first\n  let user = await request.server.cache.get(cacheKey);\n  \n  if (!user) {\n    // Fetch from database\n    user = await getUserFromDatabase(userId);\n    // Cache for 10 minutes\n    await request.server.cache.set(cacheKey, user, '10m');\n  }\n  \n  return user;\n});\n```\n\n## Configuration Options\n\nYou can customize the cache behavior by passing options to the `fuse` function:\n\n```typescript\nimport { fuse, FuseOptions } from 'fastify-fusion';\nimport fastify from 'fastify';\n\nconst app = fastify();\nconst options: FuseOptions = {\n  cache: {\n    ttl: '30m', // Default TTL of 30 minutes\n    stats: true, // Enable cache statistics\n    nonBlocking: false, // Blocking secondary operations\n  },\n};\nawait fuse(app, options);\n```\n\nYou can also disable caching entirely:\n\n```typescript\nconst options: FuseOptions = {\n  cache: false, // Disable caching\n};\n```\n\n## Cache Operations\n\nThe cache instance provides all the standard caching operations:\n\n```typescript\n// Basic get/set\nawait app.cache.set('key', 'value', '1h');\nconst value = await app.cache.get('key');\n\n// Bulk operations\nawait app.cache.setMany([\n  { key: 'key1', value: 'value1' },\n  { key: 'key2', value: 'value2', ttl: '30m' }\n]);\nconst values = await app.cache.getMany(['key1', 'key2']);\n\n// Check existence\nconst exists = await app.cache.has('key');\n\n// Delete operations\nawait app.cache.delete('key');\nawait app.cache.clear(); // Clear all\n\n// Take (get and delete)\nconst takenValue = await app.cache.take('key');\n```\n\n## Advanced Features\n\n### Memoization with wrap()\nAutomatically cache function results:\n\n```typescript\nconst expensiveFunction = app.cache.wrap(async (input) =\u003e {\n  // Expensive computation\n  return await processData(input);\n}, { ttl: '1h' });\n\n// First call computes and caches\nconst result1 = await expensiveFunction('test');\n// Second call returns cached result\nconst result2 = await expensiveFunction('test');\n```\n\n### GetOrSet Pattern\nFetch from cache or compute and store:\n\n```typescript\nconst posts = await app.cache.getOrSet('all-posts', async () =\u003e {\n  return await fetchPostsFromAPI();\n}, { ttl: '5m' });\n```\n\n### Cache Statistics\nMonitor cache performance when stats are enabled:\n\n```typescript\napp.get('/cache/stats', async (request, reply) =\u003e {\n  return {\n    hits: app.cache.stats.hits,\n    misses: app.cache.stats.misses,\n    gets: app.cache.stats.gets,\n    sets: app.cache.stats.sets,\n    count: app.cache.stats.count\n  };\n});\n```\n\n### TTL Formats\nThe cache supports both milliseconds and human-readable time formats:\n\n```typescript\n// Milliseconds\nawait app.cache.set('key', 'value', 3600000); // 1 hour\n\n// Human-readable\nawait app.cache.set('key', 'value', '1h');    // 1 hour\nawait app.cache.set('key', 'value', '30m');   // 30 minutes\nawait app.cache.set('key', 'value', '15s');   // 15 seconds\n```\n\nThe cache automatically handles cleanup and provides graceful shutdown when your Fastify server closes.\n\n# Static Paths\n\nBy default `fastify-fusion` serves static files from the `./public` directory. You can change this by passing in a `StaticOptions` object to the `fuse` function. The default configuration serves static files from the `/public` path. Here is an example of how to customize the static file serving:\n\n```typescript\nconst defaultStaticPath = [\n    {\n        dir: path.resolve('./public'),\n        path: '/',\n    },\n];\n```\n\n```typescript\nimport { fuse, FuseOptions } from 'fastify-fusion';\nimport Fastify from 'fastify';\nconst app = Fastify();\nconst options: FuseOptions = {\n  static: {\n    dir: './static/', // Serve static files from /static\n    path: '/static', // Path to the static files\n  },\n};\nawait fuse(app, options);\n```\n\n# Logging\n\nBy default, `fastify-fusion` uses Pino for logging and configures it with sensible defaults. You can customize the logging behavior by passing in a `LoggerOptions` object to the `fuse` function. The default logging configuration uses `pino-pretty` and here are the default options:\n\n```typescript\nexport const defaultLoggingOptions = {\n\ttransport: {\n\t\ttarget: 'pino-pretty',\n\t\toptions: {\n\t\t\tcolorize: true,\n\t\t\ttranslateTime: true,\n\t\t\tignore: 'pid,hostname',\n\t\t\tsingleLine: true,\n\t\t},\n\t},\n};\n```\n\nHere is an example of how to customize the logging options:\n\n```typescript\nimport { fuse, FuseOptions } from 'fastify-fusion';\nimport Fastify from 'fastify';\nconst app = Fastify();\nconst options: FuseOptions = {\n  log: {\n    level: 'info', // Set the log level\n    prettyPrint: true, // Enable pretty print for development\n  },\n};\nawait fuse(app, options);\n```\n\nIf you want to access the logger instance, you can use the `logger` function. This function will return a logger instance that you can use to log messages in your application. This will use the default logging options if none are provided. Here is an example of how to use the logger instance:\n\n```typescript\nimport { logger } from 'fastify-fusion';\n\nconst log = logger();\nlog.info('This is an info message');\n```\n\n# Helmet\n`fastify-fusion` uses `fastify-helmet` to set security headers by default. You can customize the behavior of `fastify-helmet` by passing in a `FastifyHelmetOptions` object to the `fuse` function. The default configuration sets the following headers:\n\n```typescript\nexport const defaultFastifyHelmetOptions: FastifyHelmetOptions = {\n\t// Turn off CSP (mostly for HTML) to avoid overhead\n\tcontentSecurityPolicy: false,\n\n\t// Remove the X-Power-By header\n\thidePoweredBy: true,\n\n\t// Prevent your API from being framed\n\tframeguard: {action: 'deny'},\n\n\t// Disable DNS prefetching\n\tdnsPrefetchControl: {allow: false},\n\n\t// Enable HSTS for one year on HTTPS endpoints\n\thsts: {\n\t\tmaxAge: 31_536_000, // 365 days in seconds\n\t\tincludeSubDomains: true,\n\t\tpreload: true,\n\t},\n\n\t// Block sniffing of MIME types\n\tnoSniff: true,\n\n\t// Basic XSS protections\n\txssFilter: true,\n\n\t// Don't send Referer at all\n\treferrerPolicy: {policy: 'no-referrer'},\n\n\t// Tighten cross-origin resource loading\n\tcrossOriginResourcePolicy: {policy: 'same-origin'},\n\n\t// You generally don't need the embedder/policy on an API\n\tcrossOriginEmbedderPolicy: false,\n\n\t// Leave CSP nonces off\n\t// eslint-disable-next-line @typescript-eslint/naming-convention\n\tenableCSPNonces: false,\n};\n```\n\nYou can customize the security headers by passing in a `FastifyHelmetOptions` object to the `fuse` function. The default configuration sets the following:\n\n```typescript\nimport { fuse, FuseOptions } from 'fastify-fusion';\nimport Fastify from 'fastify';\nconst app = Fastify();\nconst options: FuseOptions = {\n  helmet: {\n    contentSecurityPolicy: false, // Disable CSP for simplicity\n    crossOriginEmbedderPolicy: false, // Disable COEP for simplicity\n  },\n};\nawait fuse(app, options);\n```\n\n# Rate Limiting\n\n`fastify-fusion` uses `@fastify/rate-limit` to limit the number of requests to your API. By default, it allows 100 requests per minute per IP address. You can customize the rate limiting behavior by passing in a `RateLimitOptions` object to the `fuse` function. Here is an example of how to customize the rate limiting options:\n\n```typescript\nexport const defaultFastifyRateLimitOptions: FastifyRateLimitOptions = {\n\t// Enable rate limiting\n\tglobal: true,\n\t// Limit to 100 requests per minute\n\tmax: 500,\n\t// Time window for the rate limit\n\ttimeWindow: 60_000, // 1 minute in milliseconds\n\t// allow list for local development and testing\n\tallowList: ['127.0.0.1', '0.0.0.0'],\n};\n```\nYou can customize the rate limiting options by passing in a `RateLimitOptions` object to the `fuse` function. Here is an example of how to customize the rate limiting options:\n\n```typescript\nimport { fuse, FuseOptions } from 'fastify-fusion';\nimport Fastify from 'fastify';\nconst app = Fastify();\nconst options: FuseOptions = {\n  rateLimit: {\n\tmax: 200, // Allow 200 requests per minute per IP address\n  },\n};\nawait fuse(app, options);\n```\n\n# CORS\n\n`fastify-fusion` uses `@fastify/cors` to enable CORS for your API. By default, it allows all origins and methods. You can customize the CORS behavior by passing in a `FastifyCorsOptions` object to the `fuse` function. Here is an example of how to customize the CORS options:\n\n```typescript\nimport { fuse, FuseOptions } from 'fastify-fusion';\nimport Fastify from 'fastify';\nconst app = Fastify();\nconst options: FuseOptions = {\n  cors: {\n\torigin: [\n\t\t'https://app.yourdomain.com',\n\t\t'https://staging.yourdomain.com'\n\t],\n\tmethods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'], // Allowed methods\n\tallowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Bearer'], // Allowed headers\n\texposedHeaders: ['Content-Length', 'X-Requested-With'], // Exposed headers\n\tcredentials: true, // Allow credentials\n  },\n};\nawait fuse(app, options);\n```\n\nHere are the default CORS options:\n\n```typescript\nexport const defaultFastifyCorsOptions: FastifyCorsOptions = {\n\torigin: true, // Allow all origins\n\tmethods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'], // Allowed methods\n\tallowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Bearer'], // Allowed headers\n\texposedHeaders: ['Content-Length', 'X-Requested-With'], // Exposed headers\n\tcredentials: true, // Allow credentials\n};\n```\n\n# Open API and Docs UX\n\n`fastify-fusion` integrates with `fastify-swagger` and `scalar` to provide OpenAPI documentation and a user-friendly interface for exploring your API. By default, it serves the OpenAPI documentation at `/docs/json` and the Scalar UX at `/`. You can customize the route prefixes and the static path for the docs UX.\n\nYou can customize the OpenAPI options by passing in an `OpenApiOptions` object to the `fuse` function. Here is an example of how to customize the OpenAPI options:\n\n```typescript\nexport type OpenApiOptions = {\n\ttitle?: string;\n\tdescription?: string;\n\tversion?: string;\n\topenApiRoutePrefix?: string;\n\tdocsRoutePath?: string;\n};\n```\n\n```typescript\nimport { fuse, FuseOptions } from 'fastify-fusion';\nimport Fastify from 'fastify';\nconst app = Fastify();\nconst options: FuseOptions = {\n  openApi: {\n\topenApiRoutePrefix: '/api-docs', // Change the OpenAPI JSON route prefix\n\tdocsRoutePath: '/docs', // Change the OpenAPI docs route prefix\n  },\n};\nawait fuse(app, options);\n```\n\nYou can also set it to `false` to disable the OpenAPI documentation and Scalar UX:\n\n```typescript\nimport { fuse, FuseOptions } from 'fastify-fusion';\nimport Fastify from 'fastify';\nconst app = Fastify();\nconst options: FuseOptions = {\n\topenApi: false, // Disable OpenAPI documentation and Scalar UX\n};\nawait fuse(app, options);\n```\n\n# How to Contribute\n\nIf you want to contribute to this project, please read the [Contributing Guide](./CONTRIBUTING.md) for more information on how to get started.\n\n# Licensing and Copyright\n\nThis project is licensed under the [MIT License](./LICENSE). Copyright (c) Jared Wray.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaredwray%2Ffastify-fusion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaredwray%2Ffastify-fusion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaredwray%2Ffastify-fusion/lists"}