Fastify Firestore Service
This is a backend web framework built on Fastify and our Firestore ORM. It provides a way to define APIs, including managing data.
Topics
Key Features
- API library
- Routing
- Schemas for API inputs and responses
- CORS
- Compression
- Health check API
- Advanced error handling
- Works with the Firestore ORM library
Getting Started
Creating An API
You can create an API to fetch information from Firestore like this:
import { DatabaseAPI, EXCEPTIONS } from '@pbvision/fastify-firestore-service'
class GetOrderAPI extends DatabaseAPI {
static PATH = '/getOrder'
static DESC = `Get an order by ID, if order doesn't exists a 404 Not found
error is returned`
static INPUT = Order.KEY
static OUTPUT = {
order: Order.Schema
}
static ERRORS = {
EXCEPTIONS.NotFoundException
}
async computeResponse ({ tx, body }) {
const order = await tx.get(Order, body.id)
if (!order) {
throw new NotFoundException()
}
return { order: order.toJSON() }
}
}
You can read more about the API interface here.
Creating a Service
A service is just a server which hosts some HTTP APIs. To create a service, you
call makeService like this:
import { makeService } from '@pbvision/fastify-firestore-service'
const components = {
Order,
GetOrderAPI
}
export default async (params) => makeService({
service: 'unittest',
components,
cookie: {
secret: 'unit-test'
},
logging: {
reportErrorDetail: true, // process.env.NODE_ENV === 'localhost',
reportAllErrors: true // process.env.NODE_ENV !== 'prod'
},
swagger: {
disabled: false,
authHeaders: ['x-app', 'x-uid'],
servers: ['http://localhost:8080'],
routePrefix: '/app/docs'
},
...(params ?? {})
})
Running a Server
The makeService() helper method creates a fastify instance with a few plugins
loaded already. You may customize the fastify instance further using fastify's
customization features. To start the app, you have to call .listen() according
to
Fastify's documentation.
For example, makeService() is called in a app.js file, and the returned promise
is exported, then you write the following code to start a server:
const app = await makeTestApp(params)
app.listen({ port: 8090, host: '0.0.0.0' })
Setting up Error Reporting
Any 500 HTTP response will be logged to Sentry if:
- The
NODE_ENVenvironment variable is not localhost - The service's logging configuration has the
sentryDSNparameter set
Deliberate client errors -- a RequestError (or subclass) with HTTP status
< 500 -- are expected outcomes and are NOT reported to Sentry (they are
still logged and returned to the caller as usual). To report one anyway,
flag it at the throw site with
throw new BadRequestException('...').forceSentry(). Any error NOT thrown
through the exception classes is always reported, even if it carries a 4xx
statusCode (e.g., a third-party HTTP client error that escaped uncaught),
so unexpected errors can never be silently dropped. Errors that fire in
bursts (e.g., during a dependency outage) can be throttled with
.rateLimitSentry(windowMs).
The Sentry repo will include:
- Information about the user like ID or IP, as well as their user agent header
- Information about the request including HTTP method, URL and response code
- A unique ID for the request (the first part is a UUID identifying the fastify instance which is running, and the second part is a number indicating this requests unique number on this instance)
- Stack trace of the error
- Environment name (e.g., test or prod)
- Release = git hash of the source code for the service that's running
Components
A service is composed of components. A component can be an API, a DB Model,
etc. These components are passed to makeService() which calls the
register() method on each component so that it can do any setup it requires:
const components = {
Order,
GetOrderAPI
}
makeService({
components,
...
})
For example, API's register with fastify as a route.
Customizing Component Registration
The component system uses a visitor pattern to allow extending the registration
workflow with custom components. For example, to add a new type of component
ExampleComponent, you need to do the following:
- Subclass
ComponentRegistrar, and add aregisterExampleComponent (exampleComponent)methodimport { ComponentRegistrar } from '@pbvision/fastify-firestore-service' class CustomComponentRegistrar extends ComponentRegistrar { registerExampleComponent (exampleComponent) { // do what needs to be done } } - You can pass the new
CustomComponentRegistrarclass to makeService() like thismakeService({ RegistrarCls: CustomComponentRegistrar }) - Implement
static register (registrar)in the newExampleComponentclassclass ExampleComponent { static register (registrar) { registrar.registerExampleComponent(this) } } - Pass the new type of component as part of
componentslike thismakeService({ components: { ExampleComponent } })
Unit testing
Generating SDKs
Swagger UI
This library generates an interactive Swagger UI for all APIs at /[service]/docs.
OpenAPI SDKs
You can export APIs in an OpenAPI schema from /[service]/docs/json, and use that with OpenAPI / Swagger SDK to generate SDKs in any supported languages.
CAUTION: Swagger SDKs use positional arguments in all SDKs, maintaining backward compatibility will be challenging with vanilla SDK generators. You may customize the generators to pass keyword arguments instead for languages that support it.
Generated from 35522fb49549382afda2a2fa0e1b8b78d1bc275c