Interact with Relational Databases using TypeORM (w/JavaScript)

I bet most of the Node.js community has heard of TypeORM at some point in their life. So people working with NestJS literally know this ORM from one end to the other.

But generally those who use this ORM enjoy working with TypeScript and many tutorials and articles are aimed at using TypeORM using only TypeScript.

However you can use it with JavaScript and the only thing that changes is the way we define the models, otherwise everything is exactly the same.

In today's example we are going to create an application for a bookstore, we are going to create an Api with a simple CRUD and all we have to do is insert books, etc.

The framework I'm going to use today is Fastify, if you're used to Express, you'll feel at home because they're similar in many ways.

But today I won't explain why I prefer Fastify over Express because the focus is on using TypeORM with JavaScript.

Let's code

The database dialect I'm going to use in this example is SQLite, don't worry because the only thing that changes are the properties in the configuration object, otherwise everything is the same.

As always, first let's install the dependencies we need:

npm install fastify typeorm sqlite3

Now let's start by defining our models which in the case of TypeORM are called entities. In order to define our model, we will need to import the EntitySchema of typeorm, which we will name BookEntity.

// @src/Models/book.js

import { EntitySchema } from "typeorm";

export const BookEntity = new EntitySchema({
  // Some things come here.
});

We have to define the name of our database table, which we will call Books. Then we have to define the columns of the table that we need. We will have an attribute called id, which will be our primary and auto-incremental key. Then we will have three other attributes that will be strings, called name, description and format.

// @src/Models/book.js

import { EntitySchema } from "typeorm";

export const BookEntity = new EntitySchema({
  name: "Books",
  columns: {
    id: {
      type: Number,
      primary: true,
      generated: true,
    },
    name: {
      type: String,
    },
    description: {
      type: String,
    },
    format: {
      type: String,
    },
  },
});

In addition to defining our model, we will also need to create a class with the respective attributes of our model.

This is because when we are going to create a new book, we need to assign the data we obtained in the http request and add them to the book's instance.

In the future it will make more sense.

// @src/Models/book.js

import { EntitySchema } from "typeorm";

export class Book {
  constructor(name, description, format) {
    this.name = name;
    this.description = description;
    this.format = format;
  }
}

export const BookEntity = new EntitySchema({
  name: "Books",
  columns: {
    id: {
      type: Number,
      primary: true,
      generated: true,
    },
    name: {
      type: String,
    },
    description: {
      type: String,
    },
    format: {
      type: String,
    },
  },
});

Now we can move on to configuring the connection to the database. At this point there are several approaches that can be taken, however I will do it in a way that I find simple and intuitive.

First we need to import the createConnection() function from typeorm and then we import our BookEntity from our model.

// @src/database/index.js

import { createConnection } from "typeorm";

import { BookEntity } from "../Models/book.js";

// More stuff comes here.

The createConnection() function is asynchronous and from here on there are several approaches that can be taken, in this example I will create an asynchronous function called connection that will return our connection to the database.

And in createConnection() we will pass our connection settings, such as the dialect, our entities, among other things.

// @src/database/index.js

import { createConnection } from "typeorm";

import { BookEntity } from "../Models/book.js";

export const connection = async () => {
  return await createConnection({
    name: "default",
    type: "sqlite",
    database: "src/database/dev.db",
    entities: [BookEntity],
    logging: true,
    synchronize: true,
  });
};

Now, with our model and our connection created, we can start working on the module that will be responsible for running our application.

First we will import the app module of our application which will contain all the logic (which has not yet been created) and our function responsible for connecting to the database.

Afterwards we will create a function that will be responsible for initializing the connection to the database and starting our Api, if an error occurs we will terminate the process.

// @src/main.js

import app from "./app.js";
import { connection } from "./database/index.js";

const start = async () => {
  try {
    await connection();
    await app.listen(3333);
  } catch (err) {
    console.error(err);
    process.exit(1);
  }
};

start();

Now in our app, we'll start by importing Fastify, as well as the typeorm's getRepository() function and our model (BookEntity) along with our Book class.

In TypeORM we can choose between two patterns, Active Record and Data Mapper. When using repositories in this example, we will be using the Data Mapper pattern, to learn more about this pattern click here.

// @src/app.js

import Fastify from "fastify";
import { getRepository } from "typeorm";

import { BookEntity, Book } from "./Models/book.js";

const app = Fastify();

// More stuff comes here.

export default app;

Now we can start defining our Api routes, first I want to know if we have any books stored in our database, for that we will use typeorm's .find() method to get all the data stored in our database table.

// @src/app.js

app.get("/books", async (request, reply) => {
  const Books = getRepository(BookEntity);
  const data = await Books.find();
  return reply.send({ data });
});

However, our table is still empty, so we'll have to insert some books first.

In this case, we'll create a route to add a new book to our table, for that we'll instantiate our Book class and map each of the properties we got in the http request to our instance.

Next, we'll use the typeorm's .save() method to insert a new book into our database table.

// @src/app.js

app.post("/books", async (request, reply) => {
  const Books = getRepository(BookEntity);
  const book = new Book();
  book.name = request.body.name;
  book.description = request.body.description;
  book.format = request.body.format;
  const data = await Books.save(book);
  return reply.send({ data });
});

Now with a book inserted in the table, let's try to find just that book. For this we will create a new route that will have only one parameter, which in this case will be the id.

Then we'll use the typeorm's .findOne() method to find just the book with its id.

// @src/app.js

app.get("/books/:id", async (request, reply) => {
  const { id } = request.params;
  const Books = getRepository(BookEntity);
  const book = await Books.findOne(id);
  return reply.send({ book });
});

As we already have the book in the table and we can already get the book we specifically want, we still need to update the data for that book. For that we will use the .update() method of the typeorm and we will pass two things, the id and the updated object of the book.

// @src/app.js

app.put("/books/:id", async (request, reply) => {
  const { id } = request.params;
  const Books = getRepository(BookEntity);
  await Books.update({ id }, { ...request.body });
  const book = await Books.findOne(id);
  return reply.send({ book });
});

Last but not least, it is necessary to remove a specific book from the table. To do this, we will first have to find the book we want using the .findOne() method and we will have to pass that same book as the only argument to the .remove() method.

// @src/app.js

app.delete("/books/:id", async (request, reply) => {
  const { id } = request.params;
  const Books = getRepository(BookEntity);
  const bookToRemove = await Books.findOne(id);
  await Books.remove(bookToRemove);
  return reply.send({ book: bookToRemove });
});

The final result of the app module should look like the following:

// @src/app.js

import Fastify from "fastify";
import { getRepository } from "typeorm";

import { BookEntity, Book } from "./Models/book.js";

const app = Fastify();

app.get("/books", async (request, reply) => {
  const Books = getRepository(BookEntity);
  const data = await Books.find();
  return reply.send({ data });
});

app.post("/books", async (request, reply) => {
  const Books = getRepository(BookEntity);
  const book = new Book();
  book.name = request.body.name;
  book.description = request.body.description;
  book.format = request.body.format;
  const data = await Books.save(book);
  return reply.send({ data });
});

app.get("/books/:id", async (request, reply) => {
  const { id } = request.params;
  const Books = getRepository(BookEntity);
  const book = await Books.findOne(id);
  return reply.send({ book });
});

app.put("/books/:id", async (request, reply) => {
  const { id } = request.params;
  const Books = getRepository(BookEntity);
  await Books.update({ id }, { ...request.body });
  const book = await Books.findOne(id);
  return reply.send({ book });
});

app.delete("/books/:id", async (request, reply) => {
  const { id } = request.params;
  const Books = getRepository(BookEntity);
  const bookToRemove = await Books.findOne(id);
  await Books.remove(bookToRemove);
  return reply.send({ book: bookToRemove });
});

export default app;

If you want to see the final result of our application and you want to test locally, just clone the Github repository by accessing this link.

Conclusion

As always, I hope I was brief in explaining things and that I didn't confuse you. Have a great day! 👋 🤓

25