Skip to main content

Core Entities & the API Sketch

Level 0: Level 0: Interview & Communication Methodmedium25 minapi-designdata-modeling

Turn requirement nouns into design-relevant entities and one endpoint per requirement, with protocol, idempotency, and auth chosen deliberately.

Two artifacts that anchor the whole design

Once the problem is scoped, estimated, and its NFRs are set, the abstract problem becomes concrete in two artifacts: the core entities (the nouns your system stores) and the API sketch (the interface clients call). Doing these two before you draw any boxes anchors the whole design, because every service, cache, and datastore you add later exists to serve some entity through some endpoint.

Core entities: nouns, not schemas

Core entities come straight from the nouns in your functional requirements. For a URL shortener, "users can create a short link" and "users can be redirected" give you the entity ShortLink, and if you support accounts, User. The discipline here is to list only the fields that matter for the design, not a fully normalized schema. A ShortLink needs code (the short slug), long_url, created_at, maybe owner_id and expires_at. It does not need you to enumerate every column and index up front; that is a rabbit hole that burns time and reveals nothing about your systems thinking. You are naming entities to establish the data model's shape, not writing a migration.

Interview nuance: Resist fully normalizing the schema at this stage. Interviewers read a candidate who spends five minutes on third-normal-form column design as someone who cannot tell the load-bearing decisions from the details. Name the entity, list the three or four fields that drive the design (the ones that get indexed, sharded, or looked up), and move on.

The API sketch: one endpoint per requirement

The API sketch is one endpoint per functional requirement, with request and response shapes concrete enough to reveal the data flow. For a URL shortener:

POST /links
  req:  { "long_url": "https://...", "custom_alias?": "promo" }
  resp: { "code": "aZ3xK", "short_url": "https://sho.rt/aZ3xK" }
  (idempotency-key header so retrying the same long_url is stable)

GET /{code}
  resp: 302 redirect, Location: <long_url>
  (302 not 301, so you keep control of the link and can gather analytics;
   301 is cached by browsers and you lose the redirect and the click data)

The 301-versus-302 choice is a classic probe: 301 (permanent) is cached hard by browsers and CDNs, which is great for read latency but means the browser stops asking your service, so you lose click analytics and can never repoint the link. 302 (temporary) keeps every redirect flowing through you. Most shorteners choose 302 to retain control and analytics, accepting the extra request. Knowing that tradeoff is the point.

Check yourself
A stakeholder pushes for 301 redirects on your shortener because browsers cache them and repeat visits get faster. What does that speed cost you?

Choose the protocol deliberately

REST over HTTP is the right default for a public-facing API: it is cacheable, universally understood, and works through any client. Use gRPC for internal service-to-service calls where you control both ends and want lower latency and typed contracts (a Protobuf schema, binary framing, HTTP/2 multiplexing). Use a streaming protocol (WebSocket, Server-Sent Events, or gRPC streaming) when the server must push, like a live location feed or a chat. State which and why: "REST for the public create and redirect endpoints, gRPC between the API gateway and the internal link service."

Check yourself

Pick the right protocol for each call in a ride-sharing system.

Public endpoint where a rider requests a trip
API gateway calling the internal pricing service, both ends owned by you
Pushing the driver's live location to the rider's map every second
A third-party partner API for booking trips from another app
Trip-status updates like 'driver arriving' pushed to the rider

Two boundary concerns belong in the API sketch because they are easy to forget and interviewers look for them. Idempotency: for creates, an idempotency key makes retries safe (the client can retry a timed-out POST /links without creating duplicate codes for the same URL). Pagination and auth: any endpoint returning a list needs cursor-based pagination (?cursor=...&limit=25), and any write or private read needs an auth token at the boundary. Mentioning where these live shows you have designed real APIs, not just toy ones.

Recap: Turn requirement nouns into entities with only the design-relevant fields, define one endpoint per requirement with concrete request/response shapes, choose REST vs gRPC vs streaming deliberately, and place idempotency, pagination, and auth at the boundary.

Check yourself
A client calls 'POST /links', the response times out, and the client retries. Without an idempotency key, what does your design risk?

Apply

Your turn

The task this lesson builds to.

Define the core entities and the REST/RPC endpoints (create, redirect) for a URL shortener with request/response shapes, and note where you would choose REST vs gRPC vs a stream.

Think about

  1. Which nouns in the requirements become entities, and which fields actually matter?
  2. What is the minimal endpoint set, one per functional requirement?
  3. Where do idempotency keys, pagination, and auth belong at the boundary?

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Sketch the core entities and the API for requesting a ride and streaming live driver location to the rider in a ride-hailing app like Lyft. Specify where you use REST, where you use a streaming protocol, and why.

Think about

  1. Which fields on Ride and Driver actually drive the design (state machine, location freshness, matching keys)?
  2. Which interaction is request/response and which is a continuous server push, and what protocol fits each?
  3. Where does an idempotency key protect the rider from double-charging over a flaky mobile network?