typescript
40 lines · 6 steps
Turning TypeORM lock errors into 409s in NestJS
A NestJS exception filter catches TypeORM optimistic-lock conflicts and returns a clean HTTP 409 Conflict response.
Explained by
highlit
1import {
2 ArgumentsHost,
3 Catch,
4 ConflictException,
5 ExceptionFilter,
6 HttpStatus,
7 Logger,
8} from '@nestjs/common';
9import { HttpAdapterHost } from '@nestjs/core';
10import { OptimisticLockVersionMismatchError } from 'typeorm';
11
12@Catch(OptimisticLockVersionMismatchError)
13export class OptimisticLockExceptionFilter implements ExceptionFilter {
14 private readonly logger = new Logger(OptimisticLockExceptionFilter.name);
15
16 constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
17
18 catch(exception: OptimisticLockVersionMismatchError, host: ArgumentsHost) {
19 const { httpAdapter } = this.httpAdapterHost;
20 const ctx = host.switchToHttp();
21 const request = ctx.getRequest();
22
23 this.logger.warn(
24 `Optimistic lock conflict on ${request.method} ${request.url}: ${exception.message}`,
25 );
26
27 const conflict = new ConflictException({
28 statusCode: HttpStatus.CONFLICT,
29 error: 'StaleVersion',
30 message:
31 'The resource was modified by another request. Reload and retry with the latest version.',
32 });
33
34 httpAdapter.reply(
35 ctx.getResponse(),
36 conflict.getResponse(),
37 HttpStatus.CONFLICT,
38 );
39 }
40}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Exception filters let you translate low-level persistence errors into meaningful HTTP responses in one place.
- 2Optimistic locking surfaces concurrent writes as errors you should map to 409 Conflict, not 500.
- 3Using the HttpAdapter keeps response writing platform-agnostic across Express and Fastify.
Related explainers
python
from flask import Blueprint, jsonify from sqlalchemy import text from sqlalchemy.exc import SQLAlchemyError
Building a health check endpoint in Flask
health-check
blueprint
error-handling
Intermediate
6 steps
typescript
import { Controller, Param, Sse, MessageEvent } from '@nestjs/common'; import { Observable, interval, merge } from 'rxjs'; import { filter, map, takeWhile } from 'rxjs/operators'; import { JobService } from './job.service';
Streaming job progress with SSE in NestJS
server-sent-events
reactive-streams
rxjs
Intermediate
8 steps
typescript
import { Controller, All, Req,
Catch-all routes and error shaping in NestJS
exception-handling
routing
middleware
Intermediate
6 steps
java
@RestController @RequestMapping("/api/products") @RequiredArgsConstructor public class ProductBatchController {
Batch JSON Merge Patch in Spring
json-merge-patch
rest-api
partial-update
Intermediate
8 steps
java
public class TimedFetchService { private final ExecutorService executor = Executors.newFixedThreadPool(8); private final HttpClient httpClient = HttpClient.newHttpClient();
Enforcing HTTP timeouts with a Future
concurrency
timeouts
thread-pool
Intermediate
8 steps
typescript
import { Component } from '@angular/core'; import { RouterLink, RouterLinkActive } from '@angular/router'; import { NgFor } from '@angular/common';
Building an active-route navbar in Angular
routing
standalone-components
accessibility
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/turning-typeorm-lock-errors-into-409s-in-nestjs-explained-typescript-64b0/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.