fastify-fusion
Version:
Fastify API framework with `best practices` and `plugins` fused together to make it easy to build and maintain your API.
559 lines (441 loc) • 17.6 kB
Markdown

# fastify-fusion
[](https://codecov.io/gh/jaredwray/fastify-fusion)
[](https://github.com/jaredwray/fastify-fusion/actions/workflows/tests.yaml)
[](https://www.npmjs.com/package/fastify-fusion)
[](https://www.npmjs.com/package/fastify-fusion)
[](https://github.com/jaredwray/fastify-fusion/blob/main/LICENSE)
Fastify API framework with `best practices` and `plugins` fused together to make it easy to build and maintain your API.
# Features
- **Fuse** - Easily create a Fastify app with sensible defaults via `fuse()`.
- **Start** - Easy to start your Fastify app with sensible defaults via `start()`.
- **Cache** - High-performance layer 1/2 caching with the `cacheable` library integrated as `app.cache`.
- **OpenAPI** - OpenAPI docs generated using `fastify-swagger` and `scalar` with sensible defaults.
- **Helmet** - Security headers set using `@fastify/helmet` with sensible defaults.
- **CORS** - CORS enabled using `@fastify/cors` with sensible defaults for apis.
- **Logging** - Pino Configured using `pino-pretty` to make it easy to read and access to a `logger` instance.
- **Rate Limiting** - Rate limiting using `@fastify/rate-limit` with sensible defaults.
- **Static Paths**: Default `./public` static path and easy to add / configure your own.
- **Regularly updated**: Updated regularly to keep up with the latest Fastify and TypeScript features.
# Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Fuse Options](#fuse-options)
- [Fastify Start](#fastify-start)
- [Cache](#cache)
- [Static Paths](#static-paths)
- [Logging](#logging)
- [Helmet](#helmet)
- [Rate Limiting](#rate-limiting)
- [CORS](#cors)
- [Open API and Docs UX](#open-api-and-docs-ux)
- [How to Contribute](#how-to-contribute)
- [Licensing and Copyright](#licensing-and-copyright)
# Installation
```bash
npm install fastify-fusion fastify
```
# Usage
If you already have a Fastify app, you can use `fuse` to add the default options and plugins to your app.
```typescript
import fastify from 'fastify';
import { fuse, FuseOptions } from 'fastify-fusion';
const app = fastify();
// 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.
await fuse(app);
```
You can also pass in the `FuseOptions` to customize your fastify instance.
```typescript
import fastify from 'fastify';
import { fuse, FuseOptions } from 'fastify-fusion';
const fuseOptions: FuseOptions = {
static: true,
log: true,
helmet: false,
rateLimit: false,
cache: true
};
const app = await fuse(app, fuseOptions);
```
You 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.
```typescript
import fastify from 'fastify';
import { start, fuse } from 'fastify-fusion';
const app = fastify();
// fuse the app with default options
await fuse(app);
// start the app. Set the options with StartOptions type.
await start(app);
```
# Fuse Options
You can customize the behavior of `fastify-fusion` by passing in options to the `fuse` function or when creating a new Fastify app with `fastify()`.
```typescript
import { fuse, FuseOptions } from 'fastify-fusion';
import Fastify from 'fastify';
const app = Fastify();
const options: FuseOptions = {
helmet: {
contentSecurityPolicy: false, // Disable CSP for simplicity
},
static: {
path: '/static/', // Serve static files from /public
dir: './static', // Path to the static files
},
rateLimit: {
max: 200, // Allow 200 requests per minute per IP address
},
};
await fuse(app, options);
```
Here is the `FuseOptions` interface with all the available options:
```typescript
export type FuseOptions = {
static?: boolean | StaticOptions;
log?: boolean | LoggerOptions;
helmet?: boolean | FastifyHelmetOptions;
rateLimit?: boolean | FastifyRateLimitOptions;
cors?: boolean | FastifyCorsOptions;
openApi?: boolean | OpenApiOptions;
cache?: boolean | CacheableOptions;
};
```
By 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.
# Fastify Start
You 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.
```typescript
import { fastify, start } from 'fastify-fusion';
const app = await fastify();
start(app);
```
You can customize the port and host by passing in a `StartOptions` object to the `start` function.
```typescript
import { fastify, start, type StartOptions } from 'fastify-fusion';
const app = await fastify();
const options: StartOptions = {
port: 3001, // Set the port to 3000
host: '127.0.0.1', // Set the host to 127.0.0.1
};
start(app, options);
```
If 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.
```typescript
import { fastify, start, type StartOptions } from 'fastify-fusion';
const app = await fastify();
const options: StartOptions = {
message: (host, port) => `🌏 started successfully at http://${host}:${port}`,
};
start(app, options);
```
# Cache
`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.
## Default Configuration
```typescript
export const defaultCacheableOptions: CacheableOptions = {
ttl: "1h", // Default 1 hour TTL
stats: true, // Enable statistics by default
nonBlocking: true, // Non-blocking secondary operations
};
```
## Basic Usage
Once fused, the cache is available on your Fastify instance as `app.cache`:
```typescript
import fastify from 'fastify';
import { fuse } from 'fastify-fusion';
const app = fastify();
await fuse(app);
// Use cache in routes
app.get('/user/:id', async (request, reply) => {
const userId = request.params.id;
const cacheKey = `user:${userId}`;
// Try to get from cache first
let user = await request.server.cache.get(cacheKey);
if (!user) {
// Fetch from database
user = await getUserFromDatabase(userId);
// Cache for 10 minutes
await request.server.cache.set(cacheKey, user, '10m');
}
return user;
});
```
## Configuration Options
You can customize the cache behavior by passing options to the `fuse` function:
```typescript
import { fuse, FuseOptions } from 'fastify-fusion';
import fastify from 'fastify';
const app = fastify();
const options: FuseOptions = {
cache: {
ttl: '30m', // Default TTL of 30 minutes
stats: true, // Enable cache statistics
nonBlocking: false, // Blocking secondary operations
},
};
await fuse(app, options);
```
You can also disable caching entirely:
```typescript
const options: FuseOptions = {
cache: false, // Disable caching
};
```
## Cache Operations
The cache instance provides all the standard caching operations:
```typescript
// Basic get/set
await app.cache.set('key', 'value', '1h');
const value = await app.cache.get('key');
// Bulk operations
await app.cache.setMany([
{ key: 'key1', value: 'value1' },
{ key: 'key2', value: 'value2', ttl: '30m' }
]);
const values = await app.cache.getMany(['key1', 'key2']);
// Check existence
const exists = await app.cache.has('key');
// Delete operations
await app.cache.delete('key');
await app.cache.clear(); // Clear all
// Take (get and delete)
const takenValue = await app.cache.take('key');
```
## Advanced Features
### Memoization with wrap()
Automatically cache function results:
```typescript
const expensiveFunction = app.cache.wrap(async (input) => {
// Expensive computation
return await processData(input);
}, { ttl: '1h' });
// First call computes and caches
const result1 = await expensiveFunction('test');
// Second call returns cached result
const result2 = await expensiveFunction('test');
```
### GetOrSet Pattern
Fetch from cache or compute and store:
```typescript
const posts = await app.cache.getOrSet('all-posts', async () => {
return await fetchPostsFromAPI();
}, { ttl: '5m' });
```
### Cache Statistics
Monitor cache performance when stats are enabled:
```typescript
app.get('/cache/stats', async (request, reply) => {
return {
hits: app.cache.stats.hits,
misses: app.cache.stats.misses,
gets: app.cache.stats.gets,
sets: app.cache.stats.sets,
count: app.cache.stats.count
};
});
```
### TTL Formats
The cache supports both milliseconds and human-readable time formats:
```typescript
// Milliseconds
await app.cache.set('key', 'value', 3600000); // 1 hour
// Human-readable
await app.cache.set('key', 'value', '1h'); // 1 hour
await app.cache.set('key', 'value', '30m'); // 30 minutes
await app.cache.set('key', 'value', '15s'); // 15 seconds
```
The cache automatically handles cleanup and provides graceful shutdown when your Fastify server closes.
# Static Paths
By 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:
```typescript
const defaultStaticPath = [
{
dir: path.resolve('./public'),
path: '/',
},
];
```
```typescript
import { fuse, FuseOptions } from 'fastify-fusion';
import Fastify from 'fastify';
const app = Fastify();
const options: FuseOptions = {
static: {
dir: './static/', // Serve static files from /static
path: '/static', // Path to the static files
},
};
await fuse(app, options);
```
# Logging
By 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:
```typescript
export const defaultLoggingOptions = {
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: true,
ignore: 'pid,hostname',
singleLine: true,
},
},
};
```
Here is an example of how to customize the logging options:
```typescript
import { fuse, FuseOptions } from 'fastify-fusion';
import Fastify from 'fastify';
const app = Fastify();
const options: FuseOptions = {
log: {
level: 'info', // Set the log level
prettyPrint: true, // Enable pretty print for development
},
};
await fuse(app, options);
```
If 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:
```typescript
import { logger } from 'fastify-fusion';
const log = logger();
log.info('This is an info message');
```
# Helmet
`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:
```typescript
export const defaultFastifyHelmetOptions: FastifyHelmetOptions = {
// Turn off CSP (mostly for HTML) to avoid overhead
contentSecurityPolicy: false,
// Remove the X-Power-By header
hidePoweredBy: true,
// Prevent your API from being framed
frameguard: {action: 'deny'},
// Disable DNS prefetching
dnsPrefetchControl: {allow: false},
// Enable HSTS for one year on HTTPS endpoints
hsts: {
maxAge: 31_536_000, // 365 days in seconds
includeSubDomains: true,
preload: true,
},
// Block sniffing of MIME types
noSniff: true,
// Basic XSS protections
xssFilter: true,
// Don't send Referer at all
referrerPolicy: {policy: 'no-referrer'},
// Tighten cross-origin resource loading
crossOriginResourcePolicy: {policy: 'same-origin'},
// You generally don't need the embedder/policy on an API
crossOriginEmbedderPolicy: false,
// Leave CSP nonces off
// eslint-disable-next-line @typescript-eslint/naming-convention
enableCSPNonces: false,
};
```
You can customize the security headers by passing in a `FastifyHelmetOptions` object to the `fuse` function. The default configuration sets the following:
```typescript
import { fuse, FuseOptions } from 'fastify-fusion';
import Fastify from 'fastify';
const app = Fastify();
const options: FuseOptions = {
helmet: {
contentSecurityPolicy: false, // Disable CSP for simplicity
crossOriginEmbedderPolicy: false, // Disable COEP for simplicity
},
};
await fuse(app, options);
```
# Rate Limiting
`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:
```typescript
export const defaultFastifyRateLimitOptions: FastifyRateLimitOptions = {
// Enable rate limiting
global: true,
// Limit to 100 requests per minute
max: 500,
// Time window for the rate limit
timeWindow: 60_000, // 1 minute in milliseconds
// allow list for local development and testing
allowList: ['127.0.0.1', '0.0.0.0'],
};
```
You 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:
```typescript
import { fuse, FuseOptions } from 'fastify-fusion';
import Fastify from 'fastify';
const app = Fastify();
const options: FuseOptions = {
rateLimit: {
max: 200, // Allow 200 requests per minute per IP address
},
};
await fuse(app, options);
```
# CORS
`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:
```typescript
import { fuse, FuseOptions } from 'fastify-fusion';
import Fastify from 'fastify';
const app = Fastify();
const options: FuseOptions = {
cors: {
origin: [
'https://app.yourdomain.com',
'https://staging.yourdomain.com'
],
methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'], // Allowed methods
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Bearer'], // Allowed headers
exposedHeaders: ['Content-Length', 'X-Requested-With'], // Exposed headers
credentials: true, // Allow credentials
},
};
await fuse(app, options);
```
Here are the default CORS options:
```typescript
export const defaultFastifyCorsOptions: FastifyCorsOptions = {
origin: true, // Allow all origins
methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'], // Allowed methods
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Bearer'], // Allowed headers
exposedHeaders: ['Content-Length', 'X-Requested-With'], // Exposed headers
credentials: true, // Allow credentials
};
```
# Open API and Docs UX
`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.
You 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:
```typescript
export type OpenApiOptions = {
title?: string;
description?: string;
version?: string;
openApiRoutePrefix?: string;
docsRoutePath?: string;
};
```
```typescript
import { fuse, FuseOptions } from 'fastify-fusion';
import Fastify from 'fastify';
const app = Fastify();
const options: FuseOptions = {
openApi: {
openApiRoutePrefix: '/api-docs', // Change the OpenAPI JSON route prefix
docsRoutePath: '/docs', // Change the OpenAPI docs route prefix
},
};
await fuse(app, options);
```
You can also set it to `false` to disable the OpenAPI documentation and Scalar UX:
```typescript
import { fuse, FuseOptions } from 'fastify-fusion';
import Fastify from 'fastify';
const app = Fastify();
const options: FuseOptions = {
openApi: false, // Disable OpenAPI documentation and Scalar UX
};
await fuse(app, options);
```
# How to Contribute
If you want to contribute to this project, please read the [Contributing Guide](./CONTRIBUTING.md) for more information on how to get started.
# Licensing and Copyright
This project is licensed under the [MIT License](./LICENSE). Copyright (c) Jared Wray.