We already know about our models and their associations. In this episode, we are going to create a data layer using TypeScript and Node

We already have a project where we have set up TypeScript. To handle our models layer we will use [TypeORM](http://typeorm.io/, which is an ORM that can be used in Node, React and other platforms.

Creating the models

By default, TypeORM places all the entities in a folder called entity.

To create each entity, we will first create a test for the entity, before implementing the entity itself.

To handle the test database, we have used and changed a bit the file that TypeORM uses for testing the database. In this file, we open the connection with the test database and we close it .We also clean the database before each test.

Here is our connection method for the tests. We have environment variables for PGUSER, PGPASSWORD and DATABASE_TEST_NAME

  const connection = await createConnection({
    type: 'postgres',
    host: 'localhost',
    port: 5432,
    username: process.env.PGUSER,
    password: process.env.PGPASSWORD,
    database: process.env.DATABASE_TEST_NAME,
    synchronize: true,
    logging: false,
    entities: [ 'src/entity/**/*.ts' ],
    migrations: [ 'src/migration/**/*.ts' ],
    subscribers: [ 'src/subscriber/**/*.ts' ]
  })

Categories

Let's create our test for the category entity. To test the category, we will just initiate a new Category, set its title, and try to save it.

import { expect } from 'chai'
import { afterEach, beforeEach } from 'mocha'
import { Connection } from 'typeorm'
import { closeTestingConnections, createTestingConnections, reloadTestingDatabases } from '../../testHelpers/test-utils'
import { Category } from '../../entity/Category'

describe('[Category test]', () => {
  let connection: Connection
  beforeEach(
    async () =>
      (connection = await createTestingConnections({
        entities: ['src/entity/**/*.ts'],
        enabledDrivers: ['postgres'],
        dropSchema: true
      }))
  )
  beforeEach(() => reloadTestingDatabases(connection))
  afterEach(() => closeTestingConnections(connection))

  it('can create a category', async () => {
    const category = new Category()
    category.title = 'Category Title'

    const createdCategory = await category.save()

    expect(createdCategory.id).not.to.eq(undefined)
    expect(createdCategory.title).not.to.eq(undefined)
  })

})

Now, we can implement our Category class. It extends BaseEntity, and has the fields id, title, and slug, as well as timestamps.id is a string, because this will be a uuid. title and slug are also string fields.

A category has many threads. So, we are using here the decorator OneToMany and we pass the correct data to get the category from a thread.

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  BaseEntity,
  OneToMany,
  CreateDateColumn,
  UpdateDateColumn
} from 'typeorm'
import { Thread } from './Thread'

@Entity()
export class Category extends BaseEntity {
  @PrimaryGeneratedColumn('uuid') id: string

  @Column() title: string

  @CreateDateColumn({ type: 'timestamp' })
  insertedAt: Date

  @UpdateDateColumn({ type: 'timestamp' })
  updatedAt: Date

  @Column({ nullable: true })
  slug?: string

  @OneToMany(type => Thread, thread => thread.category)
  threads: Thread[]
}

Our test doesn't pass, because we haven’t implement Thread. Let's implement it.

Threads

Let's create our Threads test. This will be similar to what we did with Categories. We are basically testing if the relationship between threads and categories is working.

import { expect } from 'chai'
import { afterEach, beforeEach } from 'mocha'
import { Connection } from 'typeorm'
import { closeTestingConnections, createTestingConnections, reloadTestingDatabases } from '../../testHelpers/test-utils'
import { Thread } from '../../entity/Thread'
import { defaultCategory } from '../../testHelpers/generateModels'

describe('[Thread test]', () => {
  let connection: Connection
  beforeEach(
    async () =>
      (connection = await createTestingConnections({
        entities: ['src/entity/**/*.ts'],
        enabledDrivers: ['postgres'],
        dropSchema: true
      }))
  )
  beforeEach(() => reloadTestingDatabases(connection))
  afterEach(() => closeTestingConnections(connection))

  it('can create a thread', async () => {
    const category = await defaultCategory(connection)

    const thread = new Thread()
    thread.title = 'Some title'
    thread.slug = 'some-title'
    thread.category = category
    const createdCategory = await thread.save()

    expect(createdCategory.id).not.to.eq(undefined)
    expect(createdCategory.slug).not.to.eq(undefined)
  })
})

Generated Models

We will need to have some easy way to generate our models in our tests. For instance, we will need to have a generated user. We don't want to create the user in every test. We can just have a method that creates this in our test database.

We will have a method called defaultUser. It receives the database connection as an argument.

export async function defaultUser (connection) {
  const userArg = { name: 'name', username: 'username', email: 'email@example.com', password: 'password' }

  const user = connection.manager.create(User, userArg)
  user.password = userArg.password
  return user.save()
}

We will have the same thing for category.

export async function defaultCategory (connection) {
  const categoryArg = { title: 'Category Title Just Created' }
  return connection.manager.create(Category, categoryArg).save()
}

The same idea will be for thread and post. As we have relationships in the Post entity. We are going to re-use the methods we already created.

export async function defaultThread (categoryId, connection) {
  const threadArg = { title: 'Thread title', slug: 'thread-title', categoryId }

  return connection.manager.create(Thread, threadArg).save()
}

export async function defaultPost ({ body = 'Body of my text' }, connection) {
  const category = await defaultCategory(connection)
  const thread = await defaultThread(category.id, connection)
  const user = await defaultUser(connection)

  const postArg = { body, threadId: thread.id, userId: user.id }
  return connection.manager.create(Post, postArg).save()
}

We don't have all the entities created for these methods yet. We will create them in a moment. But this is the general structure that we will be using. Let's keep creating our entities and create our Thread model.

Creating the Thread Model

The Thread models will have the id as uuid, title and slug. Here we also have the relationship between categories and posts. A Thread has many posts. We represent this by using the OneToMany decorator.

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  BaseEntity,
  ManyToOne,
  OneToMany,
  CreateDateColumn,
  UpdateDateColumn
} from 'typeorm'
import { Category } from './Category'
import { Post } from './Post'

@Entity()
export class Thread extends BaseEntity {
  @PrimaryGeneratedColumn('uuid') id: string

  @Column() title: string

  @Column() slug: string

  @Column() categoryId: string

  @CreateDateColumn({ type: 'timestamp' })
  insertedAt: Date

  @UpdateDateColumn({ type: 'timestamp' })
  updatedAt: Date

  @ManyToOne(type => Category, category => category.threads)
  category: Category

  @OneToMany(type => Post, post => post.thread)
  posts: Post[]
}

Posts

Now, we can have posts. This test will be similar to the previous one. Here we’re just ensuring the relationships are working as expected.

import { expect } from 'chai'
import { afterEach, beforeEach } from 'mocha'
import { Connection } from 'typeorm'
import { closeTestingConnections, createTestingConnections, reloadTestingDatabases } from '../../testHelpers/test-utils'
import { Post } from '../../entity/Post'
import { defaultUser, defaultCategory, defaultThread } from '../../testHelpers/generateModels'

describe('[Post test]', () => {
  let connection: Connection
  beforeEach(
    async () =>
      (connection = await createTestingConnections({
        entities: ['src/entity/**/*.ts'],
        enabledDrivers: ['postgres'],
        dropSchema: true
      }))
  )
  beforeEach(() => reloadTestingDatabases(connection))
  afterEach(() => closeTestingConnections(connection))

  it('can create a post', async () => {
    const user = await defaultUser(connection)
    const category = await defaultCategory(connection)
    const thread = await defaultThread(category.id, connection)

    const post = new Post()
    post.body = 'my body'
    post.thread = thread
    post.user = user
    const createdPost = await post.save()

    expect(createdPost.id).not.to.eq(undefined)
    expect(createdPost.body).not.to.eq(undefined)
  })

})

We didn't have yet the entity User, that will be the next Entity we will create.

A post has a body that is of the type text. A post belongs to a thread, and we represent this relationship by using the ManyToOne decorator. A Post also belongs to a User.

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  BaseEntity,
  ManyToOne,
  CreateDateColumn,
  UpdateDateColumn
} from 'typeorm'
import { Thread } from './Thread'
import { User } from './User'

@Entity()
export class Post extends BaseEntity {
  @PrimaryGeneratedColumn('uuid') id: string

  @Column('text') body: string

  @Column() threadId: string

  @Column() userId: string

  @CreateDateColumn({ type: 'timestamp' })
  insertedAt: Date

  @UpdateDateColumn({ type: 'timestamp' })
  updatedAt: Date

  @ManyToOne(type => Thread, thread => thread.posts)
  thread: Thread

  @ManyToOne(type => User, user => user.posts)
  user: User
}

User

The User will be simple, we will just check the fields here and if the user was created. We are doing the password hash generation at the ORM level. So, we pass the passwordHash and we check if it's not defined and different from the initial password we passed. If it's different, it's because it's been hashed.

import { expect } from 'chai'
import { afterEach, beforeEach } from 'mocha'
import { Connection } from 'typeorm'
import { closeTestingConnections, createTestingConnections, reloadTestingDatabases } from '../../testHelpers/test-utils'
import { User } from '../../entity/User'

describe('[User test]', () => {
  let connection: Connection
  beforeEach(
    async () =>
      (connection = await createTestingConnections({
        entities: ['src/entity/**/*.ts'],
        enabledDrivers: ['postgres'],
        dropSchema: true
      }))
  )
  beforeEach(() => reloadTestingDatabases(connection))
  afterEach(() => closeTestingConnections(connection))

  it('can create a user', async () => {
    const user = new User()
    user.name = 'Josh Adams'
    user.email = 'josh@example.com'
    user.username = 'knewter'
    user.posts = []
    user.password = 'foobar'
    const createdUser = await user.save()
    expect(createdUser.id).not.to.eq(undefined)
    expect(createdUser.passwordHash).not.to.eq(undefined)
    expect(createdUser.passwordHash).not.to.eq('foobar')
  })

})

In our User model, we will have: name, email, username and password hash. We will use a listener decorator to hash the password. This listener is invoked before insertions and before updates. We will store the password hash field, but the client doesn't know about it. When creating a user, you simply pass a password.

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  BaseEntity,
  OneToMany,
  BeforeInsert,
  BeforeUpdate,
  CreateDateColumn,
  UpdateDateColumn
} from 'typeorm'
import { Post } from './Post'
import * as bcrypt from 'bcrypt'

@Entity()
export class User extends BaseEntity {
  @BeforeInsert()
  @BeforeUpdate()
  async hashPassword () {
    if (this.password) {
      this.passwordHash = await bcrypt.hash(this.password, 10)
    }
  }

  @PrimaryGeneratedColumn('uuid') id: string

  @Column() name: string

  @Column() email: string

  @Column() username: string

  public password: string

  @Column() passwordHash: string

  @CreateDateColumn({ type: 'timestamp' })
  insertedAt: Date

  @UpdateDateColumn({ type: 'timestamp' })
  updatedAt: Date

  @OneToMany(type => Post, post => post.user)
  posts: Post[]
}

We need to clean up a bit our code. We forget to import some entities on the tests.

Now, we have our models created, we can execute our tests. Our tests pass!

Creating our connection

We need to create a connection to our database. TypeORM has a createConnection method where we can specify the host, type, username, password and the entities we have.

We have environments variables for PGUSER, PGPASSWORD, and DATABASE_NAME.

import { createConnection } from 'typeorm'
import { User } from './entity/User'
import { Category } from './entity/Category'
import { Thread } from './entity/Thread'
import { Post } from './entity/Post'

createConnection({
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: process.env.PGUSER,
  password: process.env.PGPASSWORD,
  database: process.env.DATABASE_NAME,
  entities: [User, Category, Thread, Post],
  synchronize: true,
  logging: true
})
  .then(connection => {
    // Start the server
  })
  .catch(error => console.log(error))

When it creates the connection we will start our Apollo Server. We’ll cover that next

Summary

In this episode, we created our models that will be used in our back-end. We used TypeORM, an ORM which can be used with TypeScript and NodeJS. We tested everything using Mocha and chai.

Resources