Module 2 / One server, one database

Module 2 · lesson 1 of 9 · Read 9 min

Client, server, database

The three parts almost every app starts with, and what each one owns.

Almost every app you have used starts life as three parts: something on your phone or in your browser, a program running on a computer somewhere, and a place where data is kept. A client, a server and a database.

It sounds too simple to need a lesson. But most early design mistakes come from putting a job in the wrong one of these three. The price gets calculated on the phone. The server keeps the cart in memory. The database gets opened straight to the app. Each of these works in a demo and breaks in production.

So in this lesson we'll look at what each part is, and more importantly, what each part owns.

The three parts

Clientwhat the user touches
→
Serverthe rules
→
Databasethe memory
The shape almost every app starts with.
Part What it is What it owns
Client The app on a phone, a web page, a kitchen tablet Showing things, collecting input, a little local state
Server A program you run on machines you control The business rules, and deciding who may do what
Database A program built to store data safely and query it The truth: data that must survive crashes and restarts

A useful way to remember it: the client asks, the server decides, the database remembers.

Tiffin has more than one client

When people hear "client" they picture one app. Tiffin already has three:

  • The customer app on Android, iOS and the web. Browse, order, pay, track.
  • The kitchen tablet in each restaurant. See new orders, accept or reject, mark food as ready.
  • The rider app. See the pickup, mark it collected, mark it delivered.

All three talk to the same server, and the server talks to the same database.

%% caption: Tiffin version one. Three kinds of client, one server, one database.
flowchart TD
  CA(["Customer app"]) --> S["Server"]:::hot
  KT(["Kitchen tablet"]) --> S
  RA(["Rider app"]) --> S
  S --> DB[("Database")]:::store
Tiffin version one. Three kinds of client, one server, one database.

This is the first useful thing about the split. Because the rules live on the server, all three clients see the same world. When the kitchen accepts an order, the customer sees "accepted" and the rider sees a new pickup, because all three are reading the same row in the same database.

The client: show things, never decide things

The client is the part the user can see and touch. It is also the part you control least.

Once you ship an app, it runs on someone else's phone. They can be on a version from eight months ago. They can be on a train going through a tunnel. And a determined person can open it up, read its code, and send any request they like to your server, with any values they like.

That last point decides a lot. Imagine the customer app calculates the order total and sends it:

{ "restaurant_id": 42, "items": [{ "id": 7, "qty": 2 }], "total": 1 }

If the server trusts that total, someone can order two biryanis for one rupee. So the rule is:

The client is outside your trust boundary. Anything it sends is a request, not a fact.

The client can show a total, so the cart screen feels instant. But the server works out the real total from its own menu prices and charges that.

What the client is good at:

  • Drawing screens and reacting to taps quickly.
  • Keeping small local state: what's in the cart before checkout, which tab you were on, your login token.
  • Caching things it has already fetched, so it doesn't ask twice. (More on this in Module 3.)

The server: the rules live here

The server is a program you write and run on machines you control. Its job is to take a request, decide whether it's allowed and valid, do the work, and reply.

For Tiffin, the server is where these rules live:

  • A customer can only see their own orders.
  • An order can only be placed if the restaurant is open and every item is available.
  • The total is the sum of the menu prices, plus delivery fee, minus any valid coupon.
  • Only the restaurant that received an order can accept it.
  • An order can go from placed to accepted, but never from delivered back to placed.

Notice that none of these rules are about storing data. They're about deciding. That's the server's job.

Keep the server forgetful

There's one habit worth building from day one: don't keep anything important in the server's memory between requests.

It's tempting. You could keep each customer's cart in a variable on the server. It works, until you restart the server to deploy a fix and every cart in the city disappears. Or until you add a second server in Module 4, and half the requests land on the server that has never heard of that cart.

Anything that must outlive one request goes in the database (or, later, a shared cache). The server reads it, uses it, writes it back, and forgets. We'll come back to this properly in Module 4 when we talk about stateless servers.

The database: the truth

The database is where data goes to survive. When Tiffin's server crashes at 8:15pm and restarts at 8:16pm, every order placed before the crash must still be there. That's the database's job.

A database gives you things that would take years to build yourself:

  • Durability. Once it says "saved", the data survives a crash or a power cut.
  • Querying. "All placed orders for restaurant 42, oldest first" in one line, fast.
  • Transactions. Several changes that happen together or not at all. Save the order and its items, never just one of them.
  • Safe concurrency. Two riders trying to grab the same delivery at the same moment, and only one wins.

For Tiffin version one, that database will be PostgreSQL, a relational database. We'll look at why in the lesson on SQL vs NoSQL.

Why the app can't talk to the database directly

A question people ask early: if the server mostly reads and writes the database, why not let the app talk to the database directly and skip the server?

Because then the rules have nowhere to live. The database would have to trust whatever the app sends, and we've just seen that you can't trust the app. You'd also have to put database passwords inside an app anyone can download. And you couldn't change your tables without breaking every phone that still has the old app.

The server is the gatekeeper. Clients talk to it through a small, deliberate set of operations: the API. Behind it, you're free to change anything.

Where does each job go?

Here's a quick way to decide where something belongs. Ask three questions in order:

  1. Must it survive a restart, or be seen by anyone else? Then it goes in the database.
  2. Is it a rule, a price, a permission, or anything a cheater would want to change? Then it's decided on the server.
  3. Is it only about how things look, or a convenience for one user on one device? Then it can live on the client.
Job Where Why
The cart before checkout Client (and optionally saved to the server) It's one person's draft. Losing it is annoying, not costly
The order total Server Money. Never trust the client with it
Which restaurants are open Database, read by the server Everyone must see the same answer
Dark mode on or off Client Only affects one screen on one phone
The order status Database, changed only by the server Customer, kitchen and rider all depend on it

Place these jobs

For each, say client, server or database, and why: (a) the rider's current location, (b) checking the coupon FIRST50 is valid, (c) the list of dishes a customer has added to "favourites", (d) showing a spinner while the menu loads.

Show one answer

(a) Rider location: the rider app measures it, but it's stored by the server (in the database for now) because the customer needs to see it. The server should also sanity-check it, since a rider app could lie.

(b) Coupon check: server. Coupons are money. The client can show "coupon applied" but the server decides.

(c) Favourites: database, through the server. The customer expects to see them on their new phone too, so it can't live only on the old one.

(d) Spinner: client. It's purely about how the screen looks while waiting.

Why start here at all?

You might know where this course is going: load balancers, caches, replicas, queues, many services. Why spend time on three boxes?

Because every one of those later parts is just one of these three jobs, done at bigger scale. A cache is a faster copy of database data. A load balancer helps many servers act like one. A read replica is another copy of the database. If you're clear about what the client, server and database each own, you'll be clear about where every later part fits, and what it is allowed to do.

Tiffin will run on one server and one database for a surprisingly long time. The next lesson follows a single request through it.

What to remember

  1. Most apps start as a client, a server and a database. The client asks, the server decides, the database remembers.
  2. One app can have several clients. Tiffin has a customer app, a kitchen tablet and a rider app, all sharing one server and one database.
  3. The client is outside your trust boundary. Prices, permissions and rules are decided on the server.
  4. Don't keep important data in the server's memory between requests. If it must survive, it goes in the database.
  5. The database gives you durability, querying, transactions and safe concurrency. Clients never talk to it directly.