← Back to posts
Hey Friends, Busy week?
This is not a lecture from someone who always got it right, but someone who built enough real things to feel the consequences of inconsistent API design firsthand.
Here’s what’s inside:
Why API design matters even when you are just building for yourself
The decisions that compound into real pain over time
How to design APIs that your future self will actually thank you for
Real patterns you can apply to your Laravel and Next.js projects today
If this kind of no-fluff engineering content is your thing, subscribe and come build with us.
For a long time I only thought about APIs from the outside.
Consuming Resend for email, Stripe for payments, Google APIs for auth. Storing keys in .env files, masking them with clean variable names, calling the endpoint and handling what came back.
That is real API experience. It just lives on the client side of the relationship.
What I did not realize until I started building my own is that every route handler, every controller method, every JSON response I returned was an API decision. And the people consuming it, even when that person was just my own frontend or my future self three months later, felt every good and bad choice I made.
The ones I got wrong did not break immediately. They just made everything progressively harder the longer the project existed.
Here is what I learned.
An API Is a Promise
Before anything else, this is the mental model that changed how I think about it.
Every response shape, every status code, every field name is something a consumer will build against. Your frontend will write logic around it. Your mobile app will expect it. Your future self will depend on it.
The more consistent and intentional that promise is, the less painful your future will be. Break the promise and everything built on top of it breaks with it.
Name Your Endpoints Like a Human
REST APIs use URLs to describe resources and HTTP methods to describe actions. The resource is a noun. The method is already the verb.
GET /users - get all users
GET /users/1 - get one user
POST /users - create a user
PUT /users/1 - update a user
DELETE /users/1 - delete a user
The mistake that shows up constantly in AI generated and newer developer code:
GET /getUsers
POST /createNewUser
GET /fetchUserById/1
POST /doUserUpdate
The verb is already in the HTTP method. You do not need it in the URL too. Keep URLs as clean nouns. Six months from now when you are scanning a routes file trying to find something quickly, you will be grateful for this.
Pick a Response Shape and Never Deviate From It
This is the one I got wrong early and felt in my frontend code immediately.
Think of your API response like an envelope. The contents change every time. The envelope always looks the same.
Pick a shape:
json
{
"data": {},
"message": "",
"status": 200
}
Then use it everywhere without exception:
json
// GET /users
{
"data": [{ "id": 1, "name": "Fab" }],
"message": "Users retrieved",
"status": 200
}
// POST /users
{
"data": { "id": 2, "name": "New User" },
"message": "User created",
"status": 201
}
// 404 error
{
"data": null,
"message": "User not found",
"status": 404
}
Why this matters on your frontend:
javascript
// Consistent shape means one reusable handler for everything
const { data, message, status } = await apiCall()
if (status >= 400) showError(message)
else doSomethingWith(data)
Without a consistent shape every endpoint is a surprise. Your frontend becomes a mess of one-off handlers trying to account for whatever comes back. I have been there. It is not a fun codebase to work in.
Use HTTP Status Codes Properly
This one trips up newer developers more than almost anything else.
javascript
// Wrong - frontend has no idea what actually happened
res.status(200).json({ success: false, error: "User not found" })
// Right - the status code tells the story before the body is read
res.status(404).json({ message: "User not found" })
The ones worth knowing by heart:
200 - OK, everything worked
201 - Created successfully
400 - Bad request, something wrong with what was sent
401 - Not authenticated
403 - Authenticated but not authorized
404 - Not found
422 - Validation failed
500 - Server error, something broke on your end
Returning 200 for everything forces every consumer to read the body and guess. Proper status codes let your frontend branch cleanly without any guessing at all.
Think about the APIs you have consumed as a developer. The ones that return cryptic 200 responses with error flags buried in the body are the frustrating ones.
Never Expose Your Database Directly
This is critical and AI generated code gets it wrong constantly.
javascript
// Wrong - exposes password hash, internal IDs,
// timestamps the frontend never needs
return user
// Right - return only what the consumer actually needs
return {
id: user.id,
name: user.name,
email: user.email
}
Your API response shape and your database schema are two completely different things. Treat them that way.
This matters for three reasons. Security, because sensitive fields should never reach the frontend. Performance, because smaller payloads are faster. Flexibility, because if you ever change your database schema and your API is just returning the raw record, everything consuming that API breaks instantly.
Shape your responses intentionally. Return exactly what is needed and nothing more.
Version From Day One
Even on small projects. Even when you think nobody else will ever consume it.
/api/v1/users
That one prefix costs you nothing today and saves you everything later. When requirements change and you need to update how an endpoint works, you create /api/v2/users. Old consumers keep working. New ones use the updated version.
Without versioning, any change is a breaking change. You are either frozen in place or you are breaking things. Neither is a good place to be.
The Experience You Already Have
If you have consumed third-party APIs as a developer, you already know what good and bad design feels like from the other side.
The ones that feel good to use are consistent, predictable, and return exactly what you need with no surprises. Clear status codes, clean response shapes, and documentation that matches what the API actually does.
The ones that are painful are inconsistent, return different shapes depending on which endpoint you hit, bury errors in 200 responses, and expose data you never asked for.
You have felt both. Now, when you are the one designing the API, you know exactly which one you want to build.
Building something that needs a solid foundation from the start? That is what we do at FabBuilds.
Let’s Build It Beautifully,
Fab