diff --git a/README.md b/README.md index 7a40c2d..6ad2e5f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,175 @@ # postgres-schema-builder -Simple postgres schema builder leveraging Typescript's type system to enable typesafe queries +Simple postgres schema builder for Node.JS leveraging Typescript's type system to enable typesafe queries + +[![Build Status](https://travis-ci.com/yss14/postgres-schema-builder.svg?branch=master)](https://travis-ci.com/yss14/postgres-schema-builder) +[![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=yss14/postgres-schema-builder)](https://dependabot.com) + +## Installation +`npm i postgres-schema-builder` or `yarn add postgres-schema-builder` + +## Usage + +If you need a reference project which uses `postgres-schema-builder`, have a look at [musicshare](https://github.com/yss14/musicshare/tree/master/projects/backend/src/database). + +### Schema definition + +The recommended way to define your database schema is to export a namespace indicating the schema version. The namespace itself contains the table definitions. + +```typescript +// DatabaseV1.ts +import { TableSchema, ColumnType, NativeFunction, ForeignKeyUpdateDeleteRule, JSONType } from "postgres-schema-builder" + +export namespace DatabaseV1 { + const baseSchema = TableSchema({ + date_added: { type: ColumnType.TimestampTZ, nullable: false, defaultValue: { func: NativeFunction.Now } }, + date_removed: { type: ColumnType.TimestampTZ, nullable: true }, + }) + + export const users = TableSchema({ + ...baseSchema, + user_id: { type: ColumnType.Integer, primaryKey: true, unique: true }, + name: { type: ColumnType.Varchar, nullable: false }, + settings: { type: JSONType(), nullable: false }, + }) + + export const user_emails = TableSchema({ + user_id_ref: {type: ColumnType.Integer, primaryKey: true, nullable: false, foreignKeys: [{ targetTable: 'users', targetColumn: 'user_id', onDelete: ForeignKeyUpdateDeleteRule.Cascade }]}, + email: { type: ColumnType.Varchar, primaryKey: true, nullable: false }, + }) + + // ...more tables +} +``` + +All available `ColumnType`s can be found in the (`table.ts file`)[https://github.com/yss14/postgres-schema-builder/blob/master/src/table.ts#L78]. + +### Interfaces and Table API + +After defining the tables of our schema, we can create and export an interface for each table, which contains each column as `key`, as well as the respective TypeScript type infered from the column's `ColumnType`. + +Furthermore, we can also create a table object for each table entry of our schema, which provides useful API methods for typesafe queries. + +```typescript +// tables.ts +import { TableRecord, Table } from "postgres-schema-builder" +import { DatabaseV1 } from "./DatabaseV1" + +export const Tables = DatabaseV1 + +export interface IUserDBResult extends TableRecord { } +export interface IUserEMailDBResult extends TableRecord { } +// ...more interfaces, for each table one interface + +export const UsersTable = Table(Tables, 'users') +export const UserEMailsTable = Table(Tables, 'user_emails') +// ...more table objects, for each table one object +``` + +### Queries + +Now, we can use our exported table objects to create typesafe queries. + +```typescript +import { SQL } from "postgres-schema-builder" +import { UsersTable, UserEMailsTable} from "./tables.ts" + +UsersTable.create() // table create statement +UsersTable.drop() // table drop statement + +UsersTable.insert(['name'])(['Fresh Herrmann']) // insert new entry +UsersTable.insert(['name'])([null]) // compiler error, since name is not nullable +UsersTable.insertFromObj({ + name: 'Fresh Herrmann', + date_added: new Date(), + settings: {a: 42, b: 'no'}, +}) + +UsersTable.select('*', ['user_id'])([42]) // select all columns where user_id=42 +UsersTable.select(['user_id', 'name'], ['name'])(['Fresh Herrmann']) // select only user_id and name where name='Fresh Herrmann' + +UsersTable.selectAll('*') // select all entries from users +UsersTable.selectAll(['name']) // select all names from users + +UsersTable.update(['name'], ['user_id'])(['Freshly Fresh Herrmann'], [42]) // update entry's name where user_id=42 + +UsersTable.delete(['user_id'])([1]) // delete where user_id=1 +UsersTable.delete(['user_id'])(['abcd']) // compiler error, since user_id has type number + +// create custom query using a join +const query = SQL.raw(` + SELECT * + FROM ${UsersTable.name} u + INNER JOIN ${UserEMailsTable.name} e ON u.user_id = e.user_id_ref + WHERE u.date_removed IS NULL + AND u.user_id = $1; +`, [42]) +``` + +### Database Client + +`postgres-schema-builder` also provides a small database client to perform our typesafe and custom queries. + +```typescript +import { DatabaseClient } from "postgres-schema-builder" +import { Pool } from "pg" +import { UsersTable, Tables} from "./tables.ts" +import { config } from "./some-config.ts" + +const database = DatabaseClient( + new Pool({ + host: config.database.host, + port: config.database.port, + user: config.database.user, + password: config.database.password, + database: config.database.database, + }) +); + +// single query statements +await database.query( + UsersTable.create() +) +await database.query( + UsersTable.insertFromObj({ + name: 'Fresh Herrmann', + date_added: new Date(), + settings: { a: 42, b: 'no' }, + }) +) +const dbResults = await database.query(UsersTable.selectAll('*')) + +// batch queries +const insertStatements = someDataArray.map(entry => UsersTable.insertFromObj(entry)) + +await database.batch(insertStatements) + +// leverage transaction creating your database schema +const createTableStatements = composeCreateTableStatements(Tables) // performs a topological sort on your tables defined in + +await database.transaction(async (client) => { + createTableStatements.forEach(createTableStatement => client.query({ sql: createTableStatement })) +}); +``` + +## Todos + +* Improve and extend docs +* Allow `insert` and `insertFromObj` returning the inserted data +* Enable client to perform multiple queries +* Introduce database management object enabling schema versioning with migrations +* Extend test cases and improve code coverage + +## Support + +### Node.js +Currently, this package is automatically tested under Node.js versions `8 - 13`. +All build artifact are compiled to ES6. + +### PostgreSQL +Tested under `v9.6`, might work for newer versions as well. + +## Contributors +* Yannick Stachelscheid ([@yss14](https://github.com/yss14)) + +## License +This project is licensed under the [MIT](LICENSE) license. \ No newline at end of file