Back to case studies

Case study

Nalla Malayalam

A Malayalam children’s bookstore needed a website to sell books. We built the whole store: a storefront, an admin console that photographs its own books, and an API that keeps both honest.

Malayalam children’s picture books open on a table in a Kerala home
Client
Nalla Malayalam - Malayalam children’s books, Kerala
Scope
Product, architecture, build, deployment
Shape
Three applications, one database, one API
Stack
Next.js · NestJS · PostgreSQL · AWS
Status
Deployed on AWS, in launch testing

The brief

Selling books was the ask. Selling Malayalam books was the problem.

Nalla Malayalam publishes Malayalam picture books and story collections for children from birth to age twelve. Books sold through Instagram, WhatsApp and phone calls, and there was no store of their own. Every title has a Malayalam name, and much of the cover art carries dense Malayalam text.

An off-the-shelf shop would have covered the website and little else. Sales would keep arriving over WhatsApp and the phone, and the stock count has to stay right whichever way a copy leaves the shelf. Titles are bilingual, and photographing every book is expensive. The client’s spec also drew a firm line: business logic and security live only in an API, with the storefront and the admin as separate applications that only ever talk to it.

So we built one API that owns every rule, and made both front ends clients of it: a storefront for families and an admin console for the shop. Then we taught the console to photograph the books itself, generating product photos from the book covers.

What we built

Three applications, one source of truth.

Each has a different audience and a different way of going wrong. The two front ends hold no business rules, so neither can decide a price or a stock level on its own.

For families

The storefront

Shop by age, category, collection and author, with every title in Malayalam and English, and pay with Razorpay. A Next.js 16 app of twenty-five screens.

  • Guest, phone OTP, Google sign-in
  • Orders, addresses, wishlist, profile
  • Track an order without signing in
  • Blog and structured data
For the shop

The admin console

Where books are added and photographed, and where Instagram, WhatsApp, phone and walk-in sales, cash on delivery included, become orders. A Next.js 16 app of twenty-four screens.

  • AI photos from book covers
  • Shipments with courier tracking
  • Stock, coupons, offers, banners, blog
  • Roles, mandatory MFA, audit logs
For everything

The API

Owns the database and every business rule. A NestJS 11 service of one hundred and sixty-eight endpoints.

  • Prices, discounts, stock, payments server-side
  • Queues for images, email, maintenance
  • Email at every order stage
  • New-order alerts for the shop

Screens

What families see, and what the shop works in.

The storefront is where families browse, pay and track an order. The console is where the shop adds books, takes phone and WhatsApp orders, and ships them.

Nalla Malayalam product page
A product page. Every title carries its Malayalam and English name.
Nalla Malayalam storefront on a phone
The storefront on a phone.
Nalla Malayalam admin console sign-in
The admin console sign-in. Every change an admin makes lands in an audit log.
Order detail with shipment tracking
An order with its shipment and courier tracking.

Engineering

Six decisions that shaped the build.

Every one of these came from how a bookshop that sells over a website, WhatsApp and the phone actually behaves, not from a demo checkout.

The last copy can only be sold once.

A bookshop with a website and a WhatsApp number is selling the same shelf twice over. Reading the stock level and then writing an order is a race, and the last copy of a title is exactly where it gets run.

Checkout recomputes every total on the server, inside one database transaction, with the inventory rows locked. Stock is reserved the moment the order is created and released automatically if payment never arrives.

Each checkout also carries an idempotency key and a per-customer lock, so a double click or a second open tab cannot turn one purchase into two orders.

A failed payment must not empty the cart.

The first version cleared the cart as soon as the order was written. When the payment gateway then failed, the customer was told their cart had been kept, and found it empty.

Now the cart is cleared only once payment has actually started. A failed order releases its reserved stock and its coupon, so nothing stays held against a purchase that never happened.

The shop should not need a photographer.

Every book needs product photos, and photographing every title is expensive. The shop already has the one thing a photo would show: the cover.

In the console, the admin uploads a book’s front and back cover. The API generates four e-commerce photos from them with OpenAI’s image models: the front, the back, front and back together, and a stacked shot.

Each photo is a queued job the admin triggers on purpose, because every generation costs money. Photos are marked stale when a cover changes, and the prompts behind them are edited store-wide in settings.

Malayalam text is where image models break.

Image models happily redraw a cover’s typography instead of keeping it. Dense Malayalam text, barcodes and ISBNs came back garbled, which on a book’s own product photo is simply wrong.

We tested three model generations against real back covers. We also built a version that pasted the real cover pixels onto a generated scene. The text was perfect, but it looked like a sticker, not a photographed book, so we dropped it.

We kept pure reference-image editing and chose the newest model, the first in our tests to reproduce Malayalam paragraphs readably.

A children’s bookstore still gets attacked.

Attacks do not check what a site sells. An admin cookie, a rich text field and a public form are the same targets on a bookshop as anywhere else.

Cookie-authenticated writes require a same-origin request header, so a form on another site cannot act for a signed-in admin. The API rejects any field it does not expect, and admin-authored rich text passes an allowlist sanitiser.

Admins sign in with mandatory TOTP MFA and act through thirty-nine permissions grouped into roles. Every change lands in an audit log.

The server should never build anything.

The launch server is a single small instance. Building images on it ran it out of disk in the middle of a deploy.

Now GitHub Actions builds the images, tagged with the commit hash. The server pulls the image for exactly the code it has checked out, runs migrations from a separate tools image, checks health, and rolls back automatically if the new version fails.

Architecture

How the shape arrived where it did.

The three applications came from the spec, and everything around them was shaped by a single small server, in this order.

01

Three repositories, on purpose

The spec kept business rules and security out of the front ends. So the storefront and the admin are separate applications in separate repositories, and the only thing either can talk to is the API.

02

One box to launch

PostgreSQL, Redis, the API and Caddy run together on a single EC2 instance, with Caddy handling HTTPS automatically. Both front ends are deployed on AWS Amplify.

03

Uploads leave the box

Images go to S3 through the instance’s IAM role, so no storage keys live on the server. Every upload is re-encoded to WebP, with four smaller sizes alongside it.

04

Photos get cheaper to serve

A generated product photo went from a 1 MB PNG to about 300 KB as WebP. A storefront card loads an 18 KB version.

05

Deploys move off the server

When builds exhausted the instance, image builds moved to GitHub Actions and the container registry. A deploy became a pull, a migration and a health check.

By the numbers

What that adds up to.

3Applications
168API endpoints
52Database tables
49Screens
16.7kLines of TypeScript
48Automated tests

The stack

Chosen to be boring where it matters.

Nothing here is exotic. The interesting work is in the checkout transaction, the permission model and the photo pipeline, which is where it belongs.

Application

Next.js 16React 19NestJS 11TypeScriptPrismaPostgreSQL 16RedisBullMQ

Infrastructure

AWS EC2AWS AmplifyAmazon S3Amazon SESDockerCaddyGitHub ActionsCloudflare

Services and libraries

RazorpayGoogle sign-inOpenAI image modelsTipTapsharp

In short

Putting books in a cart is the easy version. The real job was a store that sells the same book over a website, WhatsApp and a phone call, and still knows how many copies are left.
KraavonProduct engineeringNalla Malayalam · Kerala2026