JSONB Fields
Postgres has rich support for storing JSON, which Joist supports.
Optional Strong Typing
Section titled “Optional Strong Typing”While Postgres does not apply a schema to jsonb columns, this can often be useful when you do actually have/know a schema for a jsonb column, but are using the jsonb column as a more succinct/pragmatic way to store nested/hierarchical data than as strictly relational tables and columns.
To support this, Joist supports both the superstruct library and Zod, which can describe both the TypeScript type for a value (i.e. Address has both as a street and a city), as well as do runtime validation and parsing of address values.
That said, if you do want to use the jsonb column effectively as an any object, the additional typing is optional, and you’ll just work with Objects instead.
Approach
Section titled “Approach”We’ll use an example of storing an Address with street and city fields within a single jsonb column.
First, define a Zod schema for the data you’re going to store in src/entities/types.ts:
import { z } from "zod";
export const Address = z.object({ street: z.string(), city: z.string(),});Then tell Joist to use this Address schema for the Author.address field in joist-config.json:
{ "entities": { "Author": { "fields": { "address": { "zodSchema": "Address@src/entities/types" } }, "tag": "a" } }}Now just run joist-codegen and the AuthorCodegen’s address field use the Address schema using Zod’s z.input and z.output inference in setter and getter respectively.
Superstruct
Section titled “Superstruct”First, define a superstruct type for the data you’re going to store in src/entities/types.ts:
import { Infer, object, string } from "superstruct";
export type Address = Infer<typeof address>;
export const address = object({ street: string(), city: string(),});Where:
addressis a structure that defines the schema/shape of the data to storeAddressis the TypeScript type system that Superstruct will derive for us
Then tell Joist to use this Address type for the Author.address field in joist-config.json:
{ "entities": { "Author": { "fields": { "address": { "superstruct": "address@src/entities/types" } }, "tag": "a" } }}Note that we’re pointing Joist at the address const.
Now just run joist-codegen and the AuthorCodegen’s address field use the Address type.
Current Limitations
Section titled “Current Limitations”There are few limitations to Joist’s current jsonb support:
-
Joist currently doesn’t support querying / filtering against
jsonbcolumns, i.e. inEntityManager.findclauses.In theory this is doable, but just hasn’t been implemented yet; Postgres supports quite a few operations on
jsonbcolumns, so it might be somewhat involved. See jsonb filtering support.Instead, for now, can you use raw SQL/knex queries and use
EntityManager.loadFromQueryto turn the low-levelauthorsrows intoAuthorentities. -
Joist currently loads all columns for a row (i.e.
SELECT * FROM authors WHERE id IN (...)), so if you have particularly largejsonbvalues in an entity’s row, then any load of that entity will also return thejsonbdata.Eventually lazy column support should resolve this, and allow marking
jsonbcolumns as lazy, such that they would not be automatically fetched with an entity unless explicitly requested as a load hint.