Skip to content

Latest commit

 

History

History
361 lines (265 loc) · 16.6 KB

README.md

File metadata and controls

361 lines (265 loc) · 16.6 KB

@hatchifyjs/express

@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

@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

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

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);
}

JSONAPIDocument

A type for JSON:API document that can be used as a request/response body.

Read more on the type

RecordObject

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

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

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]

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]

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

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

hatchedExpress.orm

A reference to the Sequelize instance when more control is needed.

hatchedExpress.orm.models.Todo.findAll()

hatchedExpress.parse[schemaName|allModels]

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

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

hatchedExpress.middleware.allModels.all

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]

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]

hatchedExpress.serialize[schemaName] has methods to transform the result of models back into a JSON:API response.