Implementing Article Publishing in a RealWorld Backend: Node.js and Prisma Workflow
Article publishing in the RealWorld backend validates the request payload against required fields, generates a unique URL slug from the title and author ID, queries for duplicates, persists the article with Prisma using relational connections for tags and authors, and maps the database record to the Conduit API format before returning the response.
The RealWorld demo application provides a full-stack Medium clone specification that backend frameworks must implement to demonstrate their capabilities. This guide examines the exact workflow for implementing article publishing in a RealWorld backend using the official Node.js and Prisma implementation from the gothinkster/realworld repository.
Validating the Article Payload
When a client sends a POST /api/articles request, the route handler in apps/api/server/routes/api/articles/index.post.ts reads the JSON body using readBody(event) and extracts the article object. The handler immediately validates that title, description, and body fields are present in the request.
If any required field is missing, the endpoint throws a 422 error to signal invalid input. This validation occurs in lines 7-22 of the route handler, ensuring only complete article data proceeds to the persistence layer.
Generating a Unique URL Slug
After validation, the backend constructs a URL-friendly slug to identify the article permanently. The implementation uses the slugify library combined with the authenticated user's ID to guarantee uniqueness across the entire platform:
const slug = `${slugify(title)}-${auth.id}`;
This approach ensures that two different users can publish articles with identical titles without collision, as the author ID appended to the slug guarantees uniqueness. The slug generation logic resides at line 24 of the same route file.
Preventing Duplicate Article Titles
Before persisting the new article, the handler queries the Article table to verify that no existing record already uses the generated slug. This duplicate check prevents users from republishing the same article twice or accidentally overwriting existing content.
If the query returns an existing article with that slug, the endpoint immediately returns a 422 error with the message indicating that the title must be unique. This safeguard appears in lines 26-36 of apps/api/server/routes/api/articles/index.post.ts.
Persisting the Article with Prisma
Once validation and uniqueness checks pass, the backend executes the primary database operation using Prisma's create method. The usePrisma().article.create call stores the article while simultaneously handling relational data through the following strategy:
- Core fields:
title,description,body, andslugare stored directly from the request data - Tag handling: The
tagListrelation usesconnectOrCreateto either link existing tags or create new ones atomically - Author linkage: The
authorrelation connects to the authenticated user viaauth.id
The Prisma query also includes related data needed for the API response:
const {
authorId,
id: articleId,
...createdArticle
} = await usePrisma().article.create({
data: {
title,
description,
body,
slug,
tagList: {
connectOrCreate: tags.map(tag => ({
create: { name: tag },
where: { name: tag },
})),
},
author: { connect: { id: auth.id } },
},
include: {
tagList: { select: { name: true } },
author: {
select: {
username: true,
bio: true,
image: true,
followedBy: true,
},
},
favoritedBy: true,
_count: { select: { favoritedBy: true } },
},
});
This database transaction, found in lines 44-81 of the route handler, efficiently creates the article record while fetching the tag names, author profile, favorite status, and favorite count in a single query.
Mapping Database Records to the API Format
The raw Prisma record contains nested relational objects that do not match the Conduit API specification. The articleMapper utility function, located in apps/api/server/utils/article.mapper.ts, transforms the database structure into the expected response format.
The mapper adds computed fields including:
- favorited: A boolean indicating whether the requesting user appears in the
favoritedByarray - favoritesCount: The total number of users who favorited the article
- author: A sub-object formatted by
authorMappercontaining profile information
The route returns the final payload using:
return { article: articleMapper(createdArticle, auth.id) };
Prisma Schema Support
The underlying data model enabling this workflow is defined in apps/api/prisma/schema.prisma. The Article model establishes the necessary relations and constraints:
model Article {
id Int @id @default(autoincrement())
slug String @unique
title String
description String
body String
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
tagList Tag[]
author User @relation("UserArticles", fields: [authorId], onDelete: Cascade, references: [id])
authorId Int
favoritedBy User[] @relation("UserFavorites")
comments Comment[]
}
The schema enforces unique slugs at the database level through the @unique attribute on the slug field, while the onDelete: Cascade setting ensures that articles are removed when their author is deleted.
Summary
- Request validation occurs immediately upon receiving
POST /api/articles, requiring title, description, and body fields to be present or returning a422error - Slug generation combines
slugify(title)with the author's ID to create unique, URL-friendly identifiers stored in theslugfield - Duplicate prevention queries the Article table before creation, rejecting requests if the generated slug already exists
- Prisma transactions use
connectOrCreatefor tag management andconnectfor author relations while including related data (tags, author profile, favorites) in a single query - Data transformation through
articleMapperconverts Prisma's relational objects into the Conduit API specification, adding computed fields likefavoritedstatus andfavoritesCount
Frequently Asked Questions
How does the RealWorld backend ensure article slugs are unique?
The backend generates slugs by combining the slugified article title with the authenticated user's ID using the pattern ${slugify(title)}-${auth.id}. This composite key guarantees uniqueness across users because each author has a distinct ID, preventing collisions even when multiple users publish articles with identical titles.
What happens if a user tries to publish an article with a duplicate title?
The route handler queries the Article table to check for an existing record with the generated slug before creating a new entry. If a match exists, the endpoint returns a 422 error with a message indicating that the title must be unique, preventing duplicate publications without altering existing data.
How are article tags handled during the publishing workflow?
The Prisma create operation uses connectOrCreate logic within the tagList relation. For each tag provided in the request, the database either connects to an existing tag record or creates a new one atomically. This approach maintains referential integrity while allowing articles to reference multiple tags without requiring separate tag creation endpoints.
What data is included in the article creation response?
The response contains a complete article object following the Conduit API specification, including the slug, title, description, body, tagList, createdAt, updatedAt, favorited status, favoritesCount, and a nested author object with profile details. The articleMapper utility in apps/api/server/utils/article.mapper.ts transforms the raw Prisma record to include these computed and relational fields.
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 →