@piggly/fastify-chassis
Version:
An ESM/CommonJS toolkit to help you to do common operations in your back-end applications with Fastify and NodeJS.
303 lines (240 loc) • 9.43 kB
Markdown
> Outdated documentation. We are working to update it.
  [](LICENSE)
An ESM/CommonJS toolkit to help you to do common operations in your back-end applications with Fastify and NodeJS.
This library was a requirement for some internal projects on our company. But it may work with another projects designed following Oriented-Object Programming pattern.
- Encapsulates Fastify into a manageable class;
- Allow to use common functions for common behavious;
- Manages JWT tokens, environment and logging.
All classes, methods and functions are well-documented with JSDoc and implementing a flexible types for TypeScript.
The `HttpInsecureServer`, `HttpSecureServer`, `Http2InsecureServer` or `Http2SecureServer` classes create and manage the `fastify` instance, bootstrap it and apply all plugins, routes, hooks attached to options. After bootstraping, it returns the `HTTPServer` instance to start, stop and restart the `fastify` instance. Below you can see a full implementation example:
> You may see a complete example on [samples](./samples) folder.
> See the [caiquearaujo/fastify-chassis-benchmarks](https://github.com/caiquearaujo/fastify-chassis-benchmarks) repository to see some benchmarks and advices.
```ts
import { FastifyReply, FastifyRequest, RouteGenericInterface } from 'fastify';
import { Server } from 'http';
// for catching log file
import path from 'path';
// plugin example
import fastifyRateLimit from '@fastify/rate-limit';
import {
BaseController
ApiServerOptions,
DefaultEnvironment,
FastifyModifierCallable,
FastifyModifiers,
HttpInsecureServer
RequestNotFoundError,
RequestServerError,
AuditRequestLogger,
SyncErrorOnDiskHandler,
} from '@piggly/fastify-chassis';
// Defining global types
type MyCurrentServer = Server;
type MyCurrentEnvironment = DefaultEnvironment;
type Request = FastifyRequest<RouteGenericInterface, MyCurrentServer>;
type Reply = FastifyReply<MyCurrentServer>;
// !! Environment
// Must be an object
const env: MyCurrentEnvironment = {
environment: 'development',
name: 'http-insecure',
port: 3005,
host: '0.0.0.0',
debug: true,
timezone: 'UTC',
log_path: path.resolve(__dirname, 'logs'),
};
// !! Routes
// Must be an instance of FastifyModifiers
// You may create a class extending BaseController
class PublicApiController extends BaseController<
MyCurrentServer,
MyCurrentEnvironment,
any // no deps
> {
public async helloWorld(request: Request, reply: Reply): Promise<void> {
return reply.send({
message: 'Hello world!',
application: this._env.name,
});
}
}
const PublicApiRoutes: FastifyModifierCallable<MyCurrentServer> = (
app: FastifyInstance<MyCurrentServer>
): Promise<void> => {
const controller = new PublicApiController(env);
app.get('/hello-world', controller.helloWorld.bind(controller));
return Promise.resolve();
};
// !! Plugins
// Must be an instance of FastifyModifiers
const rateLimitPlugin: FastifyModifierCallable<
MyCurrentServer,
MyCurrentEnvironment
> = async app => {
await app.register(fastifyRateLimit, {
max: 30,
timeWindow: '1 minute',
});
};
const plugins = new FastifyModifiers<MyCurrentServer, MyCurrentEnvironment>(
rateLimitPlugin
);
import fastifyRateLimit from '@fastify/rate-limit';
// !! Hooks
// !! Hook before call init method (register plugins/routes/etc) on bootstrapping
const beforeInit: FastifyModifierCallable<
MyCurrentServer,
MyCurrentEnvironment
> = async app => {
console.log('Do something before fastify init.');
AuditRequestLogger(
app,
env.log_path,
env.environment,
env.debug ? 'debug' : 'info'
);
};
// !! Hook after call init method (register plugins/routes/etc) on bootstrapping
const afterInit: FastifyModifierCallable<
MyCurrentServer,
MyCurrentEnvironment
> = async () => {
console.log(
'Do something after fastify init.'
);
// Such as register onClose hook
app.addHook('onClose', async () => {
await database.quit();
});
};
// !! Options
const options: ApiServerOptions<MyCurrentServer, MyCurrentEnvironment> = {
routes: new FastifyModifiers<MyCurrentServer, MyCurrentEnvironment>(
PublicApiRoutes
),
plugins,
env,
hooks: { beforeInit, afterInit },
errors: {
notFound: new RequestNotFoundError(),
unknown: new RequestServerError(),
handler: SyncErrorOnDiskHandler(env.log_path),
},
};
// !! Listen to port on fastify
new HttpInsecureServer(options)
.bootstrap()
.then(server => {
server
.start()
.then(() =>
console.log(
`⚡️ Server started ${environment.host}:${environment.port}.`
)
)
.catch((err: any) => {
console.error('❌ Server failed.');
console.error(err);
process.exit(1);
});
})
.catch((err: any) => {
console.error('❌ Server failed.');
console.error(err);
process.exit(1);
});
```
This library also help to handleing with access token based in JWT. The `JWTAccessTokenService` can issue, get, unlock request and return a middleware to be used at some route. The abstract `JWTService` does the implementation to issue/verify JWT token. By default, the implementation of `EdDSA` token is `JWTEdDSAService`.
```ts
// JWT Options
const jwt_options = {
issuer: 'string', // (required) in issue(), set the issuer as
audience: ['string'], // (required) in issue(), set one or more audiences
accept_issuer: 'string', // (required) in get(), evaluate the issuer expected
accept_audience: 'string', // (required) in get(), evaluate the audience expected
ed25519: {
public_key: string; // (required) key data, use JWTService.readKeyFileSync() or JWTService.eadKeyFileAsync() to get from file
private_key: string; // (required) key data, use JWTService.readKeyFileSync() or JWTService.eadKeyFileAsync() to get from file
},
ttl: 300, // time to live, be default 300
required_claims: ['scopes','role'], // (optional) Addional claims to be required on token
}
// JWT Service
const jwt_service = new JWTEdDSAService(jwt_options);
// Access Token Options
const access_token_options = {
unlock_by: {
role: true, // when true, evaluate role claim as expected
scope: true, // when true, evaluate scope claim as expected
origin: true, // when true, evaluate origin claim as expected
ip: true, // when true, evaluate ip claim as expected
}
};
// Errors objects
const access_token_errors = {
forbidden: () => new ForbiddenError(); // has ForbiddenError by default, but you can change it
unauthorized: () => new UnauthorizedError(); // has UnauthorizedError by default, but you can change it
missing_header: () => new MissingAuthorizationHeaderError(); // has MissingAuthorizationHeaderError by default, but you can change it
invalid_token_type: () => new InvalidAuthorizationHeaderError(); // has InvalidAuthorizationHeaderError by default, but you can change it
}
// Access token service
const access_token_service = new JWTAccessTokenService(jwt_service, access_token_options, access_token_errors);
// Use somewhere as middleware, it will set the parsed token at req.access_token
fastify.register(
(fastify, options, done) => {
fastify.addHook(
'preHandler',
this._services.AccessTokenService.middleware(
'payment.read', // scope needed on token to evaluate, may be "any" if does not need scope
'customer' // role needed on token to evaluate, may be "any" if does not need role
)
);
fastify.get('/payments', this.collection.bind(this));
done();
},
{
prefix: '/public',
}
);
```
The `JWTEdDSAService` is expecting the public/private key of type `ed25519` to sign/verify JWT tokens. You must issue it following the procedures below:
```bash
openssl genpkey -algorithm ed25519 -outform PEM -out private.pem
openssl pkey -in private.pem -pubout >> public.pem
```
> We recommend you to keep `600` or `400` cmod permissions for these files.
You may see another tools on this library, such as:
- Predefined errors;
- Singleton to get environment and logger;
- Hook for audit request to include access token data (if available) on logs;
- Date parser, pagination meta handler, and some individual functions.
Feel free to explore.
This library is ready for ES module or CommonJs module. You must add it by using Node.Js:
```bash
npm i --save @piggly/fastify-chassis
```
See the [CHANGELOG](CHANGELOG.md) file for information about all code changes.
This library uses the **Jest**. We carry out tests of all the main features of this application.
```bash
npm run test:once
```
See the [CONTRIBUTING](CONTRIBUTING.md) file for information before submitting your contribution.
- [Caique Araujo](https://github.com/caiquearaujo)
- [All contributors](../../contributors)
MIT License (MIT). See [LICENSE](LICENSE).