How the RealWorld Backend Handles Race Conditions During Concurrent Article Updates
The RealWorld backend prevents race conditions during concurrent article updates by leveraging SQLite's ACID transactions and a unique database index on the article slug, ensuring that conflicting writes fail atomically at the database level.
The gothinkster/realworld repository implements a production-grade Medium clone API that demonstrates handling concurrent article updates without traditional locking mechanisms. When multiple clients simultaneously attempt to modify the same article via PUT /api/articles/:slug, the system relies on database-level guarantees provided by Prisma and SQLite rather than application-layer mutexes or semaphores.
The Read-Modify-Write Flow
The update handler in apps/api/server/routes/api/articles/[slug]/index.put.ts follows a classic read-modify-write pattern. First, it retrieves the existing article record to verify ownership and compute potential changes. This initial fetch automatically executes within an implicit SQLite transaction boundary, establishing a consistent state view for the subsequent operations.
The handler validates ownership before processing any modifications:
const existingArticle = await usePrisma().article.findFirst({ … });
if (existingArticle.author.id !== auth.id) { … }
(see lines 12-31 of the route handler)
Defensive Application Checks
Ownership Verification
The code explicitly compares the authenticated user's ID against the article's author ID before allowing modifications. If the ownership check fails, the handler immediately throws a 403 Forbidden error, preventing unauthorized writes from reaching the database layer.
Pre-Emptive Slug Collision Detection
When a title change triggers a slug update, the application performs a defensive lookup to catch conflicts early:
const existingTitle = await usePrisma().article.findFirst({
where: { slug: newSlug },
select: { slug: true },
});
if (existingTitle) {
throw new HttpException(422, { errors: { title: ['must be unique'] } });
}
(see lines 40-51 of index.put.ts)
This application-level validation provides immediate feedback for obvious conflicts, though the database constraint serves as the ultimate authority for race condition prevention.
Database-Level Atomicity and Constraints
Unique Index Enforcement
The Prisma schema in apps/api/prisma/schema.prisma defines the slug field with a @unique attribute:
model Article {
slug String @unique
…
}
(see lines 13-14 of the schema file)
SQLite enforces this constraint atomically. If two concurrent requests pass the pre-check simultaneously, the first committed UPDATE succeeds while the second encounters a unique constraint violation. Prisma propagates this as a PrismaClientKnownRequestError, which the API translates into a 422 Unprocessable Entity response.
Transactional Isolation Guarantees
Each request operates within a single SQLite transaction started implicitly by Prisma. SQLite's ACID properties ensure that the series of reads and writes—ownership verification, slug collision checks, and the final update—execute as an atomic unit isolated from other concurrent requests. Even if two processes pass the pre-check simultaneously, the database serializes the writes, allowing only one to commit successfully.
Consistent Tag Update Handling
The handler maintains referential integrity by clearing existing tag relationships before applying updates. The disconnectArticlesTags operation and the subsequent article.update execute within the same transaction:
await disconnectArticlesTags(slug);
const updatedArticle = await usePrisma().article.update({ … });
(see lines 63-78 of the route handler)
This sequencing prevents orphaned tag relationships if the update fails after the disconnect operation, ensuring that tag modifications cannot be left half-finished due to concurrent interference or partial failures.
Summary
- Database constraints provide the primary defense against race conditions through the unique index on
slugdefined inapps/api/prisma/schema.prisma. - SQLite transactions ensure atomic execution of the read-modify-write sequence, isolating concurrent requests via ACID guarantees.
- Application-level checks verify ownership and perform pre-emptive slug collision detection to fail fast before attempting database writes.
- Tag updates maintain consistency by executing
disconnectArticlesTagsandarticle.updatewithin the same transactional boundary.
Frequently Asked Questions
What happens if two users try to update an article title simultaneously?
Both requests may pass the initial slug availability check, but SQLite's unique constraint on the slug field ensures only the first committed transaction succeeds. The second request receives a constraint violation error, which the API returns as a 422 status code with a "must be unique" validation message.
Does RealWorld use pessimistic locking to prevent race conditions?
No, the implementation relies on optimistic concurrency control through database constraints and transactional atomicity rather than explicit locks. SQLite's isolation levels and the unique index on the slug field provide sufficient protection without the complexity of application-level locking mechanisms.
How does the system handle concurrent updates to article tags?
Tag modifications are wrapped in the same SQLite transaction as the article update. The handler first calls disconnectArticlesTags to remove existing relationships, then performs the article.update with new tags. If the update fails, the transaction rolls back atomically, preventing partial or inconsistent tag states.
Can the ownership check itself be subject to race conditions?
The ownership verification occurs within the database transaction and reads committed state consistent with the transaction's start point. While concurrent ownership transfers are theoretically possible, the subsequent article.update operation uses the slug as a stable identifier, and SQLite's transactional isolation prevents phantom reads that could corrupt the authorization check.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →