@hatchifyjs/express
is an NPM package that takes a Schema and produces:
- Sequelize models,
- an expressive JSON:API restful middleware, and
- utilities for building custom restful endpoints.
The following uses hatchifyExpress
to create POST
, GET
, PATCH
, and DELETE
endpoints at /api/todos
.
import { hatchifyExpress } from "@hatchifyjs/express"
import { datetime, string, PartialSchema } from "@hatchifyjs/core"
import Express from "express"
// Define the schema
const schemas = {
Todo: {
name: "Todo",
attributes: {
name: string(),
dueDate: datetime(),
},
},
} satisfies Record<string, PartialSchema>
const app = Express()
// Pass schemas and other settings to configure hatchify
const hatchedExpress = hatchifyExpress(schemas, {
prefix: "/api",
database: { uri: "sqlite://localhost/:memory" },
})
;(async () => {
// Update the database to match the schema
await hatchedExpress.modelSync({ alter: true })
// Create CRUD endpoints for all schemas
app.use(hatchedExpress.middleware.allModels.all)
app.listen(3000, () => {
console.log("Started on http://localhost:3000")
})
})()
- Exports
- hatchifyExpress - Creates a
hatchedExpress
instance with middleware and sequelize ORMs - HatchifyExpress - A type for TypeScript usage
- JSONAPIDocument - A type for JSON:API document that can be used as a request/response body
- errorHandlerMiddleware - An error handle that produces JSON:API formatted errors
- hatchifyExpress - Creates a
hatchedExpress
- An instance ofHatchifyExpress
with middleware and sequelize ORMshatchedExpress.everything[schemaName]
- Methods to parse a request, fetch data and serialize to a JSON:API response.hatchedExpress.middleware[schemaName|allModels]
- Middleware to call the matching everything method.hatchedExpress.modelSync
- A utility function to make sure your schemas are always synced with the database.hatchedExpress.orm
- A reference to theSequelize
instance when more control is needed.hatchedExpress.parse[schemaName|allModels]
- Methods to parse a JSON:API request and return options that can be passed to the models to CRUD data.hatchedExpress.printEndpoints
- Prints a list of endpoints generated by Hatchify.hatchedExpress.schema[schemaName]
- All the Hatchify final schemas.hatchedExpress.serialize[schemaName]
- methods to transform the result of models back into a JSON:API response.
@hatchifyjs/express
provides three named exports:
- hatchifyExpress - Creates a
hatchedExpress
instance with middleware and Sequelize ORM - HatchifyExpress - A type for TypeScript fans
- errorHandlerMiddleware - A middleware to catch any Hatchify error and transform it to a proper JSON:API response
import { hatchifyExpress, HatchifyExpress, errorHandlerMiddleware } from "@hatchifyjs/express"
hatchifyExpress(schemas: Schemas, options: ExpressOptions)
is a Function
that constructs a hatchedExpress
instance with middleware and Sequelize ORM:
import { hatchifyExpress } from "@hatchifyjs/express";
const schemas = { ... }
const app = Express()
const hatchedExpress = hatchifyExpress(schemas, {
prefix: "/api",
database: { uri: "sqlite://localhost/:memory" },
})
Parameters
Property | Type | Default | Details |
---|---|---|---|
schemas | Record<string, PartialSchema> | {} | A collection of Hatchify Schemas. |
options.uri | string | sqlite://localhost/:memory | The database URI / connection string of the relational database. Ex. postgres://user:password@host:port/database?ssl=true |
options.logging | (sql: string, timing?: number) => void | undefined | A function that gets executed every time Sequelize would log something. |
options.additionalOptions | object | undefined | An object of additional options, which are passed directly to the underlying connection library (example: pg) |
See Using Postgres for instructions on how to set up HatchifyJS with postgres.
Returns
Returns a HatchifyExpress instance which is documented below.
HatchifyExpress
is the constructor function used to create a hatchedExpress instance. This TypeScript type typically isn't used directly (it's exported to support implicit typing of the return from the hatchifyExpress
constructor); however, it can be useful when defining a custom type that may reference hatchedExpress
.
import type { HatchifyExpress } from "@hatchifyjs/express"
import { hatchifyExpress } from "@hatchifyjs/express"
type Globals = {
hatchedExpress: HatchifyExpress
}
const globals: Globals = {
hatchedExpress: hatchifyExpress(schemas, options);
}
A type for JSON:API document that can be used as a request/response body.
A "flattened" JavaScript object with the record's data and associated record's data as child RecordObjects. See RecordObject for more details.
{
id: string,
name: string,
complete: boolean,
user: {
id: string,
email: string,
},
}
errorHandlerMiddleware
is a middleware to catch any Hatchify error and transform it to a proper JSON:API response. For example, the following shows a middleware that throws a fake error, preceded by errorHandlerMiddleware
:
import { errorHandlerMiddleware } from "@hatchifyjs/express"
app.use(errorHandlerMiddleware)
app.use(() => {
throw [new NotFoundError({ detail: "Fake error" })]
})
so any request will throw and handled to return an error similar to
{
"jsonapi": {
"version": "1.0"
},
"errors": [
{
"status": 404,
"code": "not-found",
"detail": "Fake error",
"title": "Resource not found."
}
]
}
hatchedExpress
is an instance of HatchifyExpress
that is returned by the hatchifyExpress
function. It provides:
- Sequelize orm models,
- an expressive JSON:API restful middleware, and
- utilities for building custom restful endpoints.
The following show some of the methods available given a SalesPerson
schema:
import { hatchifyExpress } from "@hatchifyjs/express"; const hatchedExpress = hatchifyExpress({SalesPerson: {...}, {prefix: "/api"}) hatchedExpress.schema.SalesPerson // The full schemas hatchedExpress.modelSync() // Sync the database with the schema hatchedExpress.printEndpoints() // Prints a list of endpoints generated by Hatchify // JSONAPI Middleware for CRUD operations app.use(hatchedExpress.middleware.allModels.all); app.use(hatchedExpress.middleware.SalesPerson.findAndCountAll) app.use(hatchedExpress.middleware.SalesPerson.findOne) app.use(hatchedExpress.middleware.SalesPerson.create) app.use(hatchedExpress.middleware.SalesPerson.update) app.use(hatchedExpress.middleware.SalesPerson.destroy) // Methods that do "everything" the middleware does await hatchedExpress.everything.SalesPerson.findAll("filter[name]=Jane") await hatchedExpress.everything.SalesPerson.findAndCountAll("filter[name]=Baking") await hatchedExpress.everything.SalesPerson.findOne("filter[name]=Baking") await hatchedExpress.everything.SalesPerson.create({jsonapi: {...}, data: {...}}) await hatchedExpress.everything.SalesPerson.update({jsonapi: {...}, data: {...}}, UUID) await hatchedExpress.everything.SalesPerson.destroy(UUID) // Parse JSONAPI requests into arguments for sequelize hatchedExpress.parse.SalesPerson.findAll("filter[name]=Jane") hatchedExpress.parse.SalesPerson.findAndCountAll("filter[name]=Baking") hatchedExpress.parse.SalesPerson.findOne("filter[name]=Baking") hatchedExpress.parse.SalesPerson.create({jsonapi: {...}, data: {...}}) hatchedExpress.parse.SalesPerson.update({jsonapi: {...}, data: {...}}, UUID) hatchedExpress.parse.SalesPerson.destroy(UUID) // Use the underlying sequelize methods await hatchedExpress.orm.models.SalesPerson.findAll({where: {name: "Jane"}}) await hatchedExpress.orm.models.SalesPerson.create({name: "Justin"}) await hatchedExpress.orm.models.SalesPerson.update({name: "Roye"},{where: {id: UUID}}) await hatchedExpress.orm.models.SalesPerson.destroy({where: {id: UUID}}) // Serialize sequelize data back to JSONAPI responses hatchedExpress.serialize.SalesPerson.findAll([{ id: UUID, name: "Roye" }]) hatchedExpress.serialize.SalesPerson.findAndCountAll({rows: [{id: UUID, ...}], count: 1}) hatchedExpress.serialize.SalesPerson.findOne({ id: UUID, name: "Roye" }) hatchedExpress.serialize.SalesPerson.create({ id: UUID, name: "Roye" }) hatchedExpress.serialize.SalesPerson.update({ id: UUID, name: "Roye" }) hatchedExpress.serialize.SalesPerson.destroy()
hatchedExpress.everything[schemaName|allModels]
functions very similar to the middleware
export but is expected to be used more directly, usually when defining user-created middleware.
The everything
functions takes the same properties as parse
but goes further than just building the query options. This function will do a complete operation of parsing the request, performing the ORM query operation and then serializing the resulting data to JSON:API format.
For example hatchedExpress.everything.Todo.findAll
takes the URL query params and directly returns JSON:API ready response data.
router.get("/todos", async (req: Request, res: Response) => {
const serializedTodos = await hatchedExpress.everything.Todo.findAll(req.originalUrl.split("?")[1] || "")
return res.json(serializedTodos)
})
hatchedExpress.middleware[schemaName|allModels]
All of the middleware
functions export a Express Middleware that can be passed directly to a Express app.use
or a Express router[verb]
function, mounted to a specific URL/path. The normal [schemaName] export expects to be used with:
- findAll
- findOne
- findAndCountAll
- create
- update
- destroy
hatchedExpress.modelSync({ alter: true } | { force: true } | undefined)
A utility function to make sure your schemas are always synced with the database.
If your database is created externally to Hatchify, you do not need to worry about it. Otherwise, Hatchify makes it simple by offering 3 syncing options:
hatchedExpress.modelSync()
This creates the table if it does not exist (and does nothing if it already exists)
- Postgres: Namespaces (Postgres Schemas) are handled manually
hatchedExpress.modelSync({ alter: true })
This checks what is the current state of the table in the database (which columns it has, what are their data types, etc), and then performs the necessary changes in the table to make it match the model.
- Postgres: Namespaces (Postgres Schemas) are created
hatchedExpress.modelSync({ force: true })
This creates the table, dropping it first if it already existed
- Postgres: Namespaces (Postgres Schemas) and their tables are dropped and recreated
A reference to the Sequelize
instance when more control is needed.
hatchedExpress.orm.models.Todo.findAll()
hatchedExpress.parse[schemaName|allModels] has methods to parse a JSON:API request and return options that can be passed to the models to CRUD data.
hatchedExpress.printEndpoints()
Prints a list of endpoints generated by Hatchify. This can be useful for debugging 404 errors.
Example output:
Hatchify endpoints:
GET /api/todos
POST /api/todos
GET /api/todos/:id
PATCH /api/todos/:id
DELETE /api/todos/:id
GET /api/users
POST /api/users
GET /api/users/:id
PATCH /api/users/:id
DELETE /api/users/:id
This exports a single middleware function that based on the method and the URL will call the right everything
function. It is useful as a default handler to handle all Hatchify GET
/POST
/PATCH
/DELETE
endpoints.
app.use(hatchedExpress.middleware.allModels.all)
hatchedExpress.schema[schemaName]
The schema
export provides access to all the Hatchify final schemas. This can be useful for debugging the schemas you provided.
console.log(hatchedExpress.schema)
// {
// Todo: {
// name: "Todo",
// namespace: "Admin",
// pluralName: "Todos",
// attributes: { ... },
// relationships: {
// user: { ... }
// }
// },
// ...
// }
hatchedExpress.serialize[schemaName] has methods to transform the result of models back into a JSON:API response.