Fix: TypeORM QueryFailedError and Entity Not Found
Part of: JavaScript & TypeScript Errors
Quick Answer
How to fix TypeORM QueryFailedError, entity not found errors, relation issues, missing migrations, and connection configuration problems in Node.js and NestJS applications.
The Error
TypeORM throws a QueryFailedError or entity-related error:
QueryFailedError: relation "user" does not exist
at PostgresQueryRunner.query (/app/node_modules/typeorm/driver/postgres/PostgresQueryRunner.js:...)
Error: Entity metadata for User#posts was not found.
Check if you specified a correct entity object and if it's registered in your current "default" connection.Or a relation error:
EntityNotFoundError: Could not find any entity of type "User" matching: { "id": 42 }Or a migration issue:
QueryFailedError: column "created_at" of relation "user" does not existOr connection fails with:
Error: connect ECONNREFUSED 127.0.0.1:5432
Cannot connect to the database. Connection refused.Why This Happens
TypeORM maps TypeScript classes to database tables with a complex layer of metadata, migrations, and connection configuration. This complexity means there are many points where the mapping can break.
At its core, TypeORM uses TypeScript decorators to collect metadata about entities at import time. If an entity class is never imported (or never registered in the DataSource), TypeORM has no metadata for it. The database table might exist, but TypeORM cannot map queries to it. Conversely, if the entity is registered but the table was never created (no migration, no synchronize), the SQL query references a table that does not exist at the database level.
The most dangerous configuration is synchronize: true in production. This flag compares entity definitions to the live database schema on every application startup and automatically drops and recreates columns and tables to match. A renamed field in an entity class causes the old column to be dropped — along with all its data — and a new column to be created. This works during development but destroys production data silently.
Common specific triggers:
- Entity not registered — the entity class is defined but not included in the
entitiesarray in the DataSource configuration. TypeORM can’t find metadata for unregistered entities. - Table doesn’t exist — migrations haven’t been run, or
synchronize: trueis off. The TypeScript entity exists but no corresponding database table was created. - Column added to entity but migration not generated — you added a field to an entity class, but the database column doesn’t exist yet.
- Relation metadata missing — a
@ManyToOne,@OneToMany, or@ManyToManyrelation references an entity that isn’t registered in the same DataSource. - Wrong connection name — using multiple DataSources and referencing the wrong connection name in a repository or
getConnection().
Fix 1: Register All Entities in DataSource
Every entity class must be listed in the entities array (or matched by a glob pattern):
// data-source.ts
import { DataSource } from 'typeorm';
import { User } from './entities/User';
import { Post } from './entities/Post';
import { Comment } from './entities/Comment';
export const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.DB_HOST ?? 'localhost',
port: parseInt(process.env.DB_PORT ?? '5432'),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
// Option 1: List entities explicitly
entities: [User, Post, Comment],
// Option 2: Glob pattern (picks up all entity files automatically)
// entities: [__dirname + '/entities/**/*.entity{.ts,.js}'],
migrations: [__dirname + '/migrations/**/*{.ts,.js}'],
synchronize: false, // Never true in production
});Common mistake — using a glob pattern but the compiled JS files are in a different directory than the TS source:
// Development (ts-node): __dirname = src/
entities: [__dirname + '/entities/**/*.entity.ts'],
// Production (compiled): __dirname = dist/
// This still looks for .ts files which don't exist in dist/
// Fix — use {.ts,.js} to match both
entities: [__dirname + '/entities/**/*.entity{.ts,.js}'],Fix 2: Run Pending Migrations
If the table or column doesn’t exist, generate and run migrations:
# Generate a migration from entity changes
npx typeorm migration:generate -d src/data-source.ts src/migrations/AddCreatedAt
# Run pending migrations
npx typeorm migration:run -d src/data-source.ts
# Check migration status
npx typeorm migration:show -d src/data-source.tsIn code — run migrations on app startup:
// main.ts
import { AppDataSource } from './data-source';
async function main() {
await AppDataSource.initialize();
// Run pending migrations before starting the server
const pendingMigrations = await AppDataSource.showMigrations();
if (pendingMigrations) {
console.log('Running pending migrations...');
await AppDataSource.runMigrations();
console.log('Migrations complete');
}
// Start the server
app.listen(3000);
}Warning: Never use
synchronize: truein production. It compares entity definitions to the database schema and automatically drops/creates tables and columns — including dropping columns that contain data. Use migrations instead.
Fix 3: Fix Entity Relation Errors
When a relation references an entity that isn’t in the same DataSource or has a circular dependency:
// User.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Post } from './Post.entity';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
}// Post.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './User.entity';
@Entity('posts')
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(() => User, (user) => user.posts)
@JoinColumn({ name: 'author_id' })
author: User;
}Both User and Post must be registered in the same DataSource. If only User is registered, loading user.posts will throw “Entity metadata for Post not found.”
Load relations explicitly when querying:
// Relations are NOT loaded by default
const user = await userRepo.findOne({ where: { id: 1 } });
console.log(user.posts); // undefined — not loaded
// Load with relations
const user = await userRepo.findOne({
where: { id: 1 },
relations: { posts: true }, // Explicitly load posts
});
console.log(user.posts); // Post[] ✓
// Or use QueryBuilder for complex loading
const user = await userRepo
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.id = :id', { id: 1 })
.getOne();Fix 4: Fix findOne and EntityNotFoundError
TypeORM’s findOne() returns null (not throws) when no record is found. findOneOrFail() throws EntityNotFoundError:
// findOne returns null — no exception
const user = await userRepo.findOne({ where: { id: 999 } });
if (!user) {
throw new NotFoundException('User not found');
}
// findOneOrFail throws EntityNotFoundError
try {
const user = await userRepo.findOneOrFail({ where: { id: 999 } });
} catch (e) {
if (e instanceof EntityNotFoundError) {
throw new NotFoundException('User not found');
}
throw e;
}In NestJS, handle TypeORM exceptions in a filter:
// typeorm-exception.filter.ts
import { Catch, ExceptionFilter, ArgumentsHost, HttpStatus } from '@nestjs/common';
import { EntityNotFoundError, QueryFailedError } from 'typeorm';
import { Response } from 'express';
@Catch(EntityNotFoundError, QueryFailedError)
export class TypeOrmExceptionFilter implements ExceptionFilter {
catch(exception: EntityNotFoundError | QueryFailedError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
if (exception instanceof EntityNotFoundError) {
return response.status(HttpStatus.NOT_FOUND).json({ error: 'Not found' });
}
// QueryFailedError — check for unique constraint violation
if (exception instanceof QueryFailedError) {
const detail = (exception as any).detail ?? '';
if (detail.includes('already exists')) {
return response.status(HttpStatus.CONFLICT).json({ error: 'Already exists' });
}
}
return response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error: 'Database error' });
}
}Fix 5: Fix NestJS TypeORM Module Configuration
In NestJS, entities must be registered in TypeOrmModule.forFeature() in the module that uses them:
// app.module.ts — global DataSource configuration
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.DB_HOST,
port: +process.env.DB_PORT,
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: false,
}),
UsersModule,
PostsModule,
],
})
export class AppModule {}// users.module.ts — register entities for this module
@Module({
imports: [
TypeOrmModule.forFeature([User, Post]), // Makes UserRepository and PostRepository injectable
],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}// users.service.ts — inject repositories
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private userRepo: Repository<User>,
@InjectRepository(Post)
private postRepo: Repository<Post>,
) {}
async findAll(): Promise<User[]> {
return this.userRepo.find({ relations: { posts: true } });
}
}Fix 6: Fix Migration Drift
When the database schema drifts from entity definitions (tables added manually, old migrations not applied), TypeORM’s migration generation can produce incorrect migrations:
# Check what TypeORM thinks the current schema is
npx typeorm schema:log -d src/data-source.ts
# Generate a migration to reconcile the current state
npx typeorm migration:generate -d src/data-source.ts src/migrations/SyncSchema
# Review the generated migration CAREFULLY before running
# Auto-generated migrations can drop columns you want to keep
cat src/migrations/$(ls src/migrations/ | tail -1)Run migrations in a transaction — if any migration step fails, the whole migration rolls back:
// In your migration class
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddUserTable1234567890 implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.startTransaction();
try {
await queryRunner.query(`CREATE TABLE "user" (...)`);
await queryRunner.query(`CREATE INDEX "IDX_user_email" ON "user" ("email")`);
await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
}
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "user"`);
}
}TypeORM vs Other ORMs: When QueryFailedError Patterns Differ
The same class of errors (schema mismatch, missing relations, query failures) occurs across all Node.js/TypeScript ORMs, but each handles them differently. Understanding these differences helps you troubleshoot faster — and helps you decide whether TypeORM is the right tool for your project.
TypeORM vs Prisma
Prisma uses a schema file (schema.prisma) as the single source of truth. You define models in the schema, then run prisma generate to create a type-safe client. The key difference: Prisma catches schema mismatches at build time. If you add a field to the schema but forget to run prisma migrate dev, the generated client includes the field, but the compiler will not catch the runtime error — the query will fail at the database level. However, Prisma’s migration workflow is more structured. prisma migrate dev auto-generates SQL, applies it, and records it in a _prisma_migrations table. There is no equivalent to TypeORM’s risky synchronize: true because Prisma separates development migrations (prisma migrate dev) from production deployments (prisma migrate deploy).
Error handling difference: Prisma throws PrismaClientKnownRequestError with numeric error codes (e.g., P2002 for unique constraint violations, P2025 for record not found). These are more predictable to catch than TypeORM’s generic QueryFailedError where you parse the message string.
TypeORM vs Drizzle
Drizzle takes a code-first approach like TypeORM but uses plain TypeScript objects instead of decorators. Schema is defined with pgTable(), mysqlTable(), or sqliteTable() functions. The advantage is that the schema works without experimental decorator support and without reflect-metadata. Drizzle’s query builder is closer to raw SQL, so the queries are more transparent — when a query fails, the generated SQL is easier to reason about.
Migration difference: Drizzle uses drizzle-kit to generate migrations. The generated SQL is stored as .sql files, not TypeScript classes. This makes migrations portable and reviewable without running TypeORM’s migration runner. Drizzle also supports a push command for development that is similar to TypeORM’s synchronize, but it prints a diff and asks for confirmation before applying destructive changes.
TypeORM vs Sequelize
Sequelize predates TypeORM and uses a model-definition approach. Entity registration errors are less common because Sequelize typically initializes models in a central models/index.js file that auto-discovers model files. However, Sequelize has its own class of problems: attribute vs column name confusion, association aliasing bugs, and scoped query pitfalls. Sequelize v6 added TypeScript support, but it is bolted on rather than designed in — the type safety is weaker than TypeORM’s decorator-based approach.
TypeORM vs MikroORM
MikroORM is the closest competitor to TypeORM in terms of API design. It supports both the Data Mapper and the Identity Map patterns. The key architectural difference is the Unit of Work: MikroORM tracks all entity changes in memory and flushes them to the database in a single transaction when you call em.flush(). This avoids many partial-update bugs that TypeORM’s save() method can introduce. MikroORM also enforces that entities are always loaded through the EntityManager, which prevents the “entity not registered” class of errors because the EntityManager is aware of all registered entities at initialization.
TypeORM vs Knex (Query Builder)
Knex is not an ORM — it is a query builder. It generates SQL from JavaScript method chains but does not map results to entity classes. If your TypeORM QueryFailedError stems from the ORM layer (entity metadata, relation mapping, eager loading), Knex sidesteps the problem entirely because there is no ORM layer. The trade-off is that you handle typing, validation, and relation management yourself. Knex is often the right choice when TypeORM’s abstraction creates more problems than it solves — especially for complex reporting queries or bulk operations.
TypeORM Active Record vs Data Mapper
TypeORM supports two patterns, and choosing the wrong one for your project causes friction:
Active Record — entities extend BaseEntity and have static methods like User.find(). This is simpler for small projects but couples business logic to the database layer, making testing harder.
Data Mapper — repositories handle persistence, entities are plain objects. This separates concerns but requires more boilerplate (injecting repositories, defining custom repositories). NestJS projects almost always use Data Mapper because it integrates with NestJS dependency injection.
// Active Record pattern
@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
}
// Usage: const user = await User.findOneBy({ id: 1 });
// Data Mapper pattern (NestJS)
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
}
// Usage: const user = await userRepo.findOneBy({ id: 1 });If you started with Active Record and are hitting dependency injection issues in NestJS, migrating to Data Mapper resolves them.
Still Not Working?
Enable TypeORM query logging to see the exact SQL being executed:
const AppDataSource = new DataSource({
// ...
logging: true, // Log all queries
// logging: ['query', 'error'], // Only queries and errors
logger: 'advanced-console',
});Check the compiled output. If using ts-node in development and node dist/ in production, the entity paths must work for both. Glob patterns with {.ts,.js} handle this:
entities: [path.join(__dirname, '**', '*.entity.{ts,js}')],Verify the database exists before running migrations. TypeORM doesn’t create the database itself — only tables:
psql -U postgres -c "CREATE DATABASE myapp;"
# Then run migrations
npx typeorm migration:run -d src/data-source.tsCheck for naming strategy mismatches. If you use a custom naming strategy (e.g., SnakeNamingStrategy), the column names in queries may not match what TypeORM expects. Verify that the naming strategy is applied in the DataSource config:
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
const AppDataSource = new DataSource({
// ...
namingStrategy: new SnakeNamingStrategy(),
});Inspect the migrations table directly. When migrations appear to run but the schema is wrong, check the migrations table in your database to see which migrations have actually been applied:
SELECT * FROM migrations ORDER BY timestamp DESC;If a migration is listed but its changes are missing, the migration may have been applied against a different database, or someone manually altered the table after the migration ran.
For related database issues, see Fix: Prisma Migration Failed, Fix: Postgres Relation Does Not Exist, Fix: Drizzle ORM Not Working, and Fix: NestJS Module Not Found.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Drizzle ORM Not Working — Schema Out of Sync, Relation Query Fails, or Migration Error
How to fix Drizzle ORM issues — schema definition, drizzle-kit push vs migrate, relation queries with, transactions, type inference, and common PostgreSQL/MySQL configuration problems.
Fix: Prisma Transaction Error — Transaction Already Closed or Rolled Back
How to fix Prisma transaction errors — interactive transactions vs $transaction array, error handling and rollback, nested transactions, timeout issues, and isolation levels.
Fix: Fastify Not Working — 404, Plugin Encapsulation, and Schema Validation Errors
How to fix Fastify issues — route 404 from plugin encapsulation, reply already sent, FST_ERR_VALIDATION, request.body undefined, @fastify/cors, hooks not running, and TypeScript type inference.
Fix: Neon Database Not Working — Connection Timeout, Branching Errors, or Serverless Driver Issues
How to fix Neon Postgres issues — connection string setup, serverless HTTP driver vs TCP, database branching, connection pooling, Drizzle and Prisma integration, and cold start optimization.