Skip to content

tnex overview

7sempra edited this page Jun 5, 2017 · 23 revisions

Tnex Overview

We interact with the database via Tnex, a typesafe wrapper around Knex. Tnex supports most of the same functions as Knex, which a few differences to ensure type safety (detailed below).

The basic idea behind Tnex is that each table in your database is represented by a Typescript class. This class defines the table's schema. Tnex then enforces that all of your DB operations are compatible with that schema.

Step 1: Define your table schema

Each table is represented by a Typescript class. A table declaration has three parts:

// tables.ts

// 1. Declare class. Defines table's schema.
export class Dog {
  dog_id = number();      // Column named "id" with a number type
  dog_name = string();
  dog_owner = nullable(number());
}

// 2. Create instance. Used when calling tnex functions.
export const dog = new Dog();

// 3. Register table. Associates schema with table name
register('dog', dog);
  • Each class property defines a column in the table.
  • Each property must be prefixed with the same table prefix. You can choose whatever prefix you want, as long as it's globally unique among your table classes.
  • The prefixes should only appear in your class definitions; they shouldn't be part of your actual column names.
  • Note the use of the nullable() function to define a nullable column.

Step 2: Use your schema when interacting with Tnex

import { dog } from './tables';
import { Tnex, val } from './tnex';

function getOwnedDogs(db: Tnex, owner: number) {
  return db
      .select(dog)
      .columns(
          'dog_id',
          'dog_name',
          )
      .where('dog_owner', '=', val(owner))
      .run();
}

Notes:

  • Unlike with Knex, we use the columns() function to specify which columns we want to select.
  • Note the use of val() when comparing to a
  • We have to call run() when we've completed our call chain (or we won't get a Promise back).
  • Typescript will automatically infer the return type for us, so we usually don't need to specify one. It can sometimes be convenient to declare that a db function returns a specific interface, though.
  • The inferred return type of this function is Promise<Pick<Dog, 'dog_id'|'dog_name'>>. The Promise<> part should be familiar. Pick<T, K> means "given an interface T, return a subset of it that includes just the keys K". So Pick<Dog, 'dog_id'|'dog_name'> is equivalent to { dog_id: number, dog_name: string }.
Clone this wiki locally