← Back to DevBytes

Building Modern Web Apps with TypeORM: Complete Guide

What is TypeORM?

TypeORM is an Object-Relational Mapping (ORM) library for TypeScript and JavaScript (Node.js) that runs on major database platforms such as PostgreSQL, MySQL, MariaDB, SQLite, and Microsoft SQL Server. It provides a way to map your application's classes (entities) to database tables, allowing you to interact with the database using object-oriented code instead of raw SQL. TypeORM supports both the Active Record and Data Mapper patterns, offers automatic migrations, and integrates seamlessly with modern frameworks like Express, NestJS, and Next.js.

Why TypeORM Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

In modern web development, managing database interactions efficiently is crucial. TypeORM matters because it:

Getting Started

Installation

Initialize a new Node.js project and install TypeORM along with your preferred database driver. For PostgreSQL:

npm init -y
npm install typeorm reflect-metadata pg
npm install -D typescript @types/node

Basic Configuration

Create a tsconfig.json and enable decorators and emitDecoratorMetadata:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

Define an ormconfig.json or use a programmatic configuration:

{
  "type": "postgres",
  "host": "localhost",
  "port": 5432,
  "username": "postgres",
  "password": "password",
  "database": "testdb",
  "entities": ["src/entity/**/*.ts"],
  "migrations": ["src/migration/**/*.ts"],
  "synchronize": true,
  "logging": false
}

Creating Your First Entity

An entity is a class mapped to a database table. Create src/entity/User.ts:

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column()
  firstName!: string;

  @Column()
  lastName!: string;

  @Column({ unique: true })
  email!: string;
}

Establishing a Connection

In your main entry file (e.g., src/index.ts), import reflect-metadata and create a connection:

import "reflect-metadata";
import { createConnection } from "typeorm";
import { User } from "./entity/User";

createConnection().then(async connection => {
  console.log("Connected to database");
  const userRepository = connection.getRepository(User);
  const user = new User();
  user.firstName = "John";
  user.lastName = "Doe";
  user.email = "john@example.com";
  await userRepository.save(user);
  console.log("User saved:", user);
}).catch(error => console.log(error));

Run with npx ts-node src/index.ts. TypeORM will create the table and insert the record.

Core Concepts

Entities & Decorators

Entities are decorated classes. Common decorators:

Repositories

Repositories provide an abstraction over entity operations. Use connection.getRepository(Entity) or Entity.getRepository() (Active Record). Example:

const userRepo = connection.getRepository(User);

// Find all
const users = await userRepo.find();

// Find by ID
const user = await userRepo.findOne(1);

// Update
await userRepo.update(1, { lastName: "Smith" });

// Delete
await userRepo.delete(1);

Relations

Define relationships between entities using decorators:

Example: a User that has many Posts:

// src/entity/User.ts
import { OneToMany } from "typeorm";
import { Post } from "./Post";

@Entity()
export class User {
  // ... other columns

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

// src/entity/Post.ts
import { ManyToOne, JoinColumn } from "typeorm";

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column()
  title!: string;

  @ManyToOne(() => User, user => user.posts)
  @JoinColumn({ name: "userId" })
  user!: User;
}

Query Builder

For complex queries, use the QueryBuilder:

const users = await connection
  .getRepository(User)
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.posts", "post")
  .where("user.email LIKE :email", { email: "%@example.com" })
  .orderBy("user.id", "DESC")
  .take(10)
  .getMany();

Migrations

TypeORM can generate and run migrations to keep the schema in sync with your entities. First, configure CLI in package.json:

"scripts": {
  "typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js"
}

Generate a migration after adding a new column:

npm run typeorm migration:generate -- -n AddAgeToUser

Run migrations:

npm run typeorm migration:run

Practical Examples

Building a REST API with Express

Install Express:

npm install express body-parser
npm install -D @types/express

Create src/app.ts:

import "reflect-metadata";
import { createConnection } from "typeorm";
import express from "express";
import { User } from "./entity/User";

const app = express();
app.use(express.json());

createConnection().then(connection => {
  const userRepo = connection.getRepository(User);

  // GET /users
  app.get("/users", async (req, res) => {
    const users = await userRepo.find();
    res.json(users);
  });

  // POST /users
  app.post("/users", async (req, res) => {
    const { firstName, lastName, email } = req.body;
    const user = userRepo.create({ firstName, lastName, email });
    await userRepo.save(user);
    res.status(201).json(user);
  });

  // GET /users/:id
  app.get("/users/:id", async (req, res) => {
    const user = await userRepo.findOne(req.params.id);
    if (!user) return res.status(404).json({ error: "User not found" });
    res.json(user);
  });

  // PUT /users/:id
  app.put("/users/:id", async (req, res) => {
    const user = await userRepo.findOne(req.params.id);
    if (!user) return res.status(404).json({ error: "User not found" });
    userRepo.merge(user, req.body);
    await userRepo.save(user);
    res.json(user);
  });

  // DELETE /users/:id
  app.delete("/users/:id", async (req, res) => {
    const result = await userRepo.delete(req.params.id);
    if (result.affected === 0) return res.status(404).json({ error: "User not found" });
    res.status(204).send();
  });

  app.listen(3000, () => console.log("Server running on port 3000"));
}).catch(error => console.log(error));

Using Relations in Queries

To load a user with their posts:

const userWithPosts = await userRepo.findOne(id, { relations: ["posts"] });

Or with QueryBuilder:

const user = await userRepo
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.posts", "post")
  .where("user.id = :id", { id })
  .getOne();

Best Practices

Conclusion

TypeORM provides a powerful and type-safe way to interact with databases in modern TypeScript applications. By leveraging its entity system, relations, query builder, and migration tools, developers can build scalable, maintainable web apps faster and with fewer errors. Whether you are building a small API or a large enterprise system, following the best practices outlined in this guide will help you avoid common pitfalls and keep your codebase clean. Start integrating TypeORM into your next project and experience the productivity boost of a full-featured ORM designed for the TypeScript ecosystem.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles