Column types
numeric
Because not every database numeric type can fit into JS number type, some types will be returned as strings.
Numeric types can have numeric operators in WHERE conditions.
// signed two-byte integer
t.smallint() // -> number
// signed four-byte integer
t.integer() // -> number
// signed eight-byte integer
t.bigint() // -> string
// exact numeric of selectable precision
t.numeric(precision?: number, scale?: number) // -> string
// decimal is an alias for numeric
t.decimal(precision?: number, scale?: number) // -> string
// single-precision floating-point number (4 bytes)
t.real() // -> number
// double-precision floating-point number (8 bytes)
t.doublePrecision() // -> number
// autoincrementing integer (GENERATED BY DEFAULT AS IDENTITY)
t.identity() // -> number
// autoincrementing two-byte integer
t.smallSerial() // -> number
// autoincrementing four-byte integer
t.serial() // -> number
// autoincrementing eight-byte integer
t.bigSerial() // -> stringAs listed in code comments above, bigint, numeric, decimal, and bigSerial have string output.
You can set up parsing to a number type, (remember this can cause bugs on large numbers):
t.bigint().parse(parseInt);Or bigint Postgres type can be parsed to bigint JavaScript type, but be aware that such values should be explicitly turned to a string when preparing JSON response:
t.bigint().parse(BigInt);Numeric-type columns support the following where operators:
db.someTable.where({
numericColumn: {
// lower than
lt: value,
// lower than or equal to
lte: value,
// greater than
gt: value,
// greater than or equal to
gte: value,
// between x and y
between: [x, y],
},
});text
t.text()is for an unlimited databasetexttype.t.varchar(limit?: number)is for text with a limit on a database level. If you don't specify a limit, it is identical to theTEXTPostgres type.t.string(limit = 255)is the same asvarcharwith 255 default limit.
Text types can have numeric operators in WHERE conditions.
// text with unlimited length
t.text() // -> string
// variable-length text with limit
t.varchar(limit?: number) // -> string
// `varchar` type with optional limit defaulting to 255.
t.string(limit?: number = 255) // -> stringThe char database type isn't added because it is discouraged by Postgres.
citext
citext is a database type that behaves almost exactly like text, but is case-insensitive in all operations.
To use it, first enable citext extension, create migration:
npm run db new enableCitextimport { change } from '../dbScript';
change(async (db) => {
await db.createExtension('citext');
});npm run db migrateAnd now citext is available and can be used just as a text type. It supports the same operators.
// text variable unlimited length
t.citext(); // -> stringtsvector
For full text search: define a generated column for a text vector.
See generated migration method.
// generate a `ts_vector` from other text columns
t.tsvector().generated(['title', 'body']).searchIndex();tsquery
For full text search to store queries.
// A tsquery value stores lexemes that are to be searched for
t.tsquery(); // -> stringdate and time
Datetime types can have date operators in WHERE conditions.
// 4 bytes date (no time of day)
t.date() // -> string
// timestamp with time zone (8 bytes)
t.timestamp(precision?: number) // -> string
// timestamp without time zone (8 bytes), not recommended
t.timestampNoTZ(precision?: number) // -> string
// time without time zone (8 bytes)
// format is 00:00:00
t.time(precision?: number) // -> string
// time with time zone is not added because it should never be used, according to Postgres docs.Time with time zone is not included because it's discouraged by Postgres docs.
date, timestamp, and timestampNoTZ can be customized with methods asNumber and asDate to parse database values into number and JS Date object respectively.
export const BaseTable = createBaseTable({
columnTypes: (t) => ({
...t,
// or use `.asDate()` to work with Date objects
timestamp: () => t.timestamp().asNumber(),
}),
});
// timestamp columns now are returned as numbers, or as Date objects if you choose `asDate`:
const { updatedAt, createdAt } = await db.table.take();When filtering by timestamp fields, creating or updating records, you can use dates encoded as strings, numbers or Date objects:
// filter, update, create with a Date object:
const date = new Date();
db.table.where({ createdAt: date });
db.table.find(id).update({ ...data, createdAt: date });
db.table.create({ ...data, createdAt: date });
// filter, update, create with a ISO encoded date string
const string = new Date().toISOString();
db.table.where({ createdAt: string });
db.table.find(id).update({ ...data, createdAt: string });
db.table.create({ ...data, createdAt: string });
// filter, update, create with a number retrieved from `getTime`
const number = new Date().getTime();
db.table.where({ createdAt: number });
db.table.find(id).update({ ...data, createdAt: number });
db.table.create({ ...data, createdAt: number });interval
// interval [ fields ] [ (p) ] 16 bytes time interval -178000000 years 178000000 years 1 microsecond
t.interval(fields?: string, precision?: number) // -> PostgresInterval objectThe interval type takes two optional parameters:
The first parameter is a string containing YEAR, MONTH, DAY, HOUR, and so on, check the full list in Postgres docs here.
The second parameter specifies the number of fractional digits retained in the second field.
The output of the interval column is an object containing years, month, and other fields:
type Interval = {
years?: number;
months?: number;
days?: number;
hours?: number;
minutes?: number;
seconds?: number;
};
const result: Interval = await Table.get('intervalColumn');boolean
Boolean returns true or false.
// 1 byte, true or false
t.boolean(); // -> booleanUUID
The data type uuid stores Universally Unique Identifiers (UUID).
// UUID stores Universally Unique Identifiers (UUID)
t.uuid(); // -> string, example: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11When using it as a primary key, it will automatically get a gen_random_uuid default.
export class Table extends BaseTable {
readonly table = 'table';
columns = this.setColumns((t) => ({
id: t.uuid().primaryKey(),
name: t.text(),
}));
}
// id is generated in the database
db.table.create({ name: 'Joe' });To discard the default, use default(null):
id: t.uuid().primaryKey().default(null),If you'd like to use a different default, primaryKey will respect it:
export class Table extends BaseTable {
readonly table = 'table';
columns = this.setColumns((t) => ({
id: t
.uuid()
.default(() => makeOwnUUID())
.primaryKey(),
name: t.text(),
}));
}
// custom function will be used for the id
db.table.create({ name: 'Joe' });enum
First argument is the name of an enum in the database, the second is an array of possible values:
export class Table extends BaseTable {
readonly table = 'table';
columns = this.setColumns((t) => ({
enumColumn: t.enum('enumName', ['value1', 'value2', 'value3']),
}));
}For convenience and to avoid duplication, you can define enum column in columnTypes of BaseTable, then reuse it in multiple tables:
export const BaseTable = createBaseTable({
columnTypes: (t) => ({
...t,
orderStatus: () =>
t.enum('orderStatus', ['pending', 'cancelled', 'processed']),
}),
});
export class Table extends BaseTable {
readonly table = 'table';
columns = this.setColumns((t) => ({
// it still can be chained with common column methods
orderStatus: t.orderStatus().nullable(),
}));
}json
Postgres supports two types of JSON: json is for storing JSON strings as they were saved, and jsonb is stored in binary format and allows additional methods.
export class Table extends BaseTable {
readonly table = 'table';
columns = this.setColumns((t) => ({
json: t.jsonText(),
jsonB: t.json(),
}));
}When using ORM without a validation library, you can set an arbitrary type to json. Make sure to only save properly validated data.
export class Table extends BaseTable {
readonly table = 'table';
columns = this.setColumns((t) => ({
data: t.json<{
age: number;
name: string;
description: string | null;
tags: string[];
}>(),
}));
}When having a validation library enabled, json accepts a callback where you can define a validation schema. If omitted, the type is unknown.
import { z } from 'zod';
import { object, number, string, optional, array } from 'valibot';
export class Table extends BaseTable {
readonly table = 'table';
columns = this.setColumns((t) => ({
dataZod: t.json(
z.object({
age: z.number(),
name: z.string(),
description: z.string().optional(),
tags: z.string().array(),
}),
),
// or
dataValibot: t.json(
object({
age: number(),
name: string(),
description: optional(string()),
tags: array(string()),
}),
),
}));
}json columns support the following where operators:
db.someTable.where({
jsonColumn: {
// check if the JSON value in the column is a superset of the provided value
jsonSupersetOf: { key: 'value' },
// check if the JSON value in the column is a subset of the provided value
jsonSubsetOf: { key: 'value' },
},
});When inserting or updating, the json column will use JSON.stringify to serialize data, except for when null is passed.
// `data` will be set to a database null, not JSON null:
await db.post.create({ data: null });
await db.post.find(1).update({ data: null });To insert or update JSON null, provide SQL for this:
import { sql } from './baseTable';
// 'null' is in single quotes
await db.post.create({ data: () => sql`'null'` });geometry
Geometric types are not parsed and returned as strings as the database returns them.
// point 16 bytes Point on a plane (x,y)
t.point(); // -> string
// line 32 bytes Infinite line {A,B,C}
t.line(); // -> string
// lseg 32 bytes Finite line segment [(x1,y1),(x2,y2)]
t.lseg(); // -> string
// box 32 bytes Rectangular box ((x1,y1),(x2,y2))
t.box(); // -> string
// path 16+16n bytes Closed path (similar to polygon) ((x1,y1),...)
// path 16+16n bytes Open path [(x1,y1),...]
t.path(); // -> string
// polygon 40+16n bytes Polygon (similar to closed path) ((x1,y1),...)
t.polygon(); // -> string
// circle 24 bytes Circle <(x,y),r> (center point and radius)
t.circle(); // -> stringnetwork addresses
// CIDR 7 or 19 bytes IPv4 and IPv6 networks
t.cidr(); // -> string, example: 192.168.100.128/25
// inet 7 or 19 bytes IPv4 and IPv6 hosts and networks
t.inet(); // -> string, example: 192.168.100.128/25
// macaddr 6 bytes MAC addresses
t.macaddr(); // -> string, example: 08:00:2b:01:02:03
// macaddr8 8 bytes MAC addresses (EUI-64 format)
t.macaddr8(); // -> string, example: 08:00:2b:ff:fe:01:02:03binary
The bytea data type allows storage of binary strings.
It has a default parser to parse strings like \x74657874 to node.js Buffers. If you define a custom parse, the argument will be this string.
t.bytea(); // -> Bufferbit string
it strings are strings of 1's and 0's. They can be used to store or visualize bit masks.
// Bit strings are strings of 1's and 0's.
// They can be used to store or visualize bit masks.
// There are two SQL bit types: bit(n) and bit varying(n), where n is a positive integer.
t.bit(); // -> string
// bit varying(n), where n is a positive integer
t.bitVarying(); // -> stringarray
// array of another column type
t.array(t.text()); // -> array of argument typeSee array operators for WHERE conditions.
unsupported types
For user-defined custom types, or if some database type is not supported yet, use type and as to treat this column as other type:
t.type('type_name').as(t.integer());domain
Domain is a custom database type that allows to predefine a NOT NULL and a CHECK (see postgres tutorial).
In same way as with type, specify as(otherType) to treat this column in queries as the other type:
t.domain('domainName').as(t.integer());money
For currency amount (8 bytes)
t.money(); // -> string, example: '$12.34'xml
XML data type can be used to store XML data
t.xml(); // -> stringPostgis geography
There's only a very basic support for postgis, open an issue if you need some more.
Here is a geography point type:
t.geography.point();In the migration the type will have the default 4326 SRID. p The input and output type is:
type PostgisPoint {
lon: number;
lat: number;
srid?: number; // absent for the default 4326
}