Adding TypeORM
TypeORM is our preferred library for persistance. Coupled with the choice to implement both Active Record
or the Data Mapper
pattern, it’s a flexible design choice.
Additionaly it supports many popular SQL dialects such as MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases out of the box, along with expermiental access for MongoDB.
Assumptions
Configuration
Install drivers
yarn add typeorm
And the relevant database driver for your project.
Create relevant folders
mkdir src/entities
mkdir migrations
ormconfig.ts
Create an ormconfig.ts
in your PROJECT_ROOT
.
import dotenv from 'dotenv-safe'
// TODO: Improve imports so it's clear that this is from typeorm
// eg. import { SnakeCaseNamingStrategy } from '@enso/typeorm'
import { SnakeCaseNamingStrategy } from '@ensojs/framework'
dotenv.load()
/**
* https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md#what-is-connectionoptions
*
* @type {ConnectionOptions}
*/
module.exports = {
type: process.env.DB_TYPE,
url: process.env.DB_CONNECTION_URL,
synchronize: false,
logging: true,
namingStrategy: new SnakeCaseNamingStrategy(),
entities: [
'src/entities/*.ts',
'src/entities/index.ts'
],
migrations: [
'migrations/*.ts'
],
cli: {
entitiesDir: 'src/entities',
migrationsDir: 'migrations'
}
}
Adding additional Environment dependencies
Enso only ships with ENVIRONMENT
and PORT
as part of its default enviroment. So lets create our own custom Environment and add in the required TypeORM attributes.
// file src/config/interfaces.ts
export interface IEnvironmentConfig {
ENVIRONMENT: string
PORT: number
// ++ TypeORM
DB_TYPE: string
DB_CONNECTION_URL: string
}
We need to also inform our server that we want to use our custom IEnvironmentConfig
instead of the default Enso environment.
// file src/config/env.ts
import dotenv from 'dotenv-safe'
// import { IEnvironmentConfig } from '@ensojs/framework'
import { IEnvironmentConfig } from './interfaces'
dotenv.config({
allowEmptyValues: false
})
export const env: IEnvironmentConfig = {
ENVIRONMENT: process.env.ENVIRONMENT!,
PORT: parseInt(process.env.PORT!),
DB_TYPE: process.env.DB_TYPE!,
DB_CONNECTION_URL: process.env.DB_CONNECTION_URL!,
}
Synchronously load the container
Add the connection to your registry and await
the createConnection
to guarantee the connection is available for injection.
// file: src/config/registry.ts
import { Container } from 'inversify'
import Debug from 'debug'
const debug = Debug('enso:createRegistry')
import { container } from './container'
import { createConnection, Connection } from 'typeorm'
import { IEnvironmentConfig } from './interfaces'
import { $b } from './bindings'
import { env } from './env'
export const createRegistry = async (): Promise<Container> => {
// postgres
const connectionOptions = await require('./../../ormconfig')
const connection = await createConnection(connectionOptions)
// Bind Application-instance-specific values
container.bind<IEnvironmentConfig>($b.Environment).toConstantValue(env)
container.bind<Connection>(Connection).toConstantValue(connection)
debug('createRegistry() - Done')
return container
}
Update our server.ts
Use await to resolve a fully container from our registry instead of loading the container asynchronously.
import 'reflect-metadata'
import { App } from './App'
import Debug from 'debug'
const debug = Debug('enso:server')
import { env } from './config/env'
// import { container } from './config/container'
import { createRegistry } from './config/registry'
(async () => {
try {
debug('============================================')
debug('> Starting server...')
debug('============================================')
debug('> Creating registry for dependency injection')
const container = await createRegistry()
const app = new App(env)
await app.build(container)
await app.start()
debug('')
debug('✔ [nodejs] %s', process.version)
debug('')
debug(
'✔ API server listening on port %d in [%s] mode',
env.PORT,
env.ENVIRONMENT
)
} catch (e) {
debug(e)
process.exit(1)
}
})()
👊
Your server should now boot up and connect to a datasource.
# test it with
yarn dev
Entities
https://github.com/typeorm/typeorm#step-by-step-guide
Migrations
TypeORM exposes an excellent CLI.
However we want to be writing our code in Typescript. So we need to transpile our code.
Alternativly; We can port over the commands we want in our package.json
and/or expose the CLI..
{
"scripts": {
"typeorm": "ts-node --transpile-only -r tsconfig-paths/register ../../node_modules/typeorm/cli.js",
"migration:create": "ts-node --transpile-only -r tsconfig-paths/register ../../node_modules/typeorm/cli.js migration:create",
"migration:revert": "ts-node --transpile-only -r tsconfig-paths/register ../../node_modules/typeorm/cli.js migration:revert",
"migration:run": "ts-node --transpile-only -r tsconfig-paths/register ../../node_modules/typeorm/cli.js migration:run",
"schema:drop": "ts-node --transpile-only -r tsconfig-paths/register ../../node_modules/typeorm/cli.js schema:drop"
}
}
We can then use the commands via yarn.
yarn typeorm
yarn migration:create
- Create a new migrationyarn migration:revert
- Revert a migrationyarn migration:run
- Run pending migrationsyarn schema:drop
- Drop a database
TIP
A working example of this recipe can be found in the packages/koa-typeorm monorepo