Home Work
SaaS developmentAI developmentAPI developmentMobile app developmentGoogle Ads managementHeadless ShopifyLaravel developmentNext.js developmentReact developmentTypeScript engineeringUI/UX designSEO & AEOEcommerce development
AI solutionsB2B platformsE-commerceHospitalityLead generationLogisticsEducationProcess automationSaaS platformsStartup MVPReal estateHealthcare
LegalHealthcareReal estateFinanceHospitality
The HagueRotterdamAmsterdamUtrechtEindhovenAlmereBredaArnhemNijmegenTilburgEnschedeGroningenLeidenDelftZoetermeerDen Bosch
Studio
AboutProcessLabBlogContact
Development

API development best practices that actually scale

MBy M. Tufan, Co-founder · Published May 2026 · 8 min read
QUICK ANSWER

A good API is predictable, versioned, and documented. Pick REST for most CRUD platforms and GraphQL only when clients need wildly varying data shapes. Secure it with OAuth2 or token auth, add rate-limiting from day one, version through the URL (/v1/), and document with OpenAPI. At NedDev, APIs run on Laravel 12, with Sanctum for auth and automatic OpenAPI specs.

Nine out of ten API problems we run into during audits are not about speed. They are about inconsistency. One endpoint returns 200 on a failure, another throws an empty null, and version breaks land in production unnoticed. An API is a contract between you and everyone who calls it. Treat it that way, and the vast majority of your integration misery disappears.

REST or GraphQL: choose deliberately

Most platforms we build run on REST. It is predictable, cacheable over HTTP, and every developer understands it without explanation. For ClaimHandler, REST was the logical choice: cases, documents, and users are clearly defined resources with predictable relationships. You know exactly which URL returns which data.

GraphQL tackles a real problem, but a very specific one: clients that need wildly varying data shapes and would otherwise force you into ten round-trips. Think of a mobile app, a dashboard, and a widget that each want a different subset of the same data. In that scenario, GraphQL prevents over-fetching and under-fetching. Without that problem, GraphQL mostly adds complexity: HTTP caching no longer works on its own, rate-limiting based on query cost is a discipline of its own, and an unintentionally heavy query can take down your database.

  • Choose REST when your resources are stable and you want to take advantage of HTTP caching.
  • Choose GraphQL when you have many different clients with sharply diverging data requests.
  • On the fence? Start with REST. Migrating to GraphQL can happen later, per domain. The other way around is far more painful.

A middle path we see more and more often: REST for the bulk, with a single GraphQL endpoint for the dashboard that genuinely needs flexible queries. You do not have to religiously pick one camp.

Versioning, auth, and rate-limiting

Version from the first line of code. We put the version in the URL: /api/v1/dossiers. That is visible, debuggable in logs, and you can run v2 alongside v1 without breaking existing clients. Header-based versioning sounds more elegant but is harder to debug in practice: a log line does not immediately tell you which version was called. Keep it simple and visible.

A breaking change deserves a clear deprecation strategy. Announce v1 as deprecated, give clients a window of, say, six months, and during that period send a Deprecation header so integrators see it coming in their own logs. Quietly removing a field is the fastest way to lose the trust of your integration partners.

For authentication, we use Sanctum by default in Laravel 12 for first-party clients and token auth or OAuth2 for third-party integrations. The rule is hard: no endpoint is public unless that is explicitly the intent. Default deny, not default allow. One forgotten authorization check on a single endpoint is enough for a data breach, so we test authorization just as seriously as functionality.

Rate-limiting belongs in there from day one, not when things go wrong but before. A few concrete limits that work in practice:

  • 60 requests per minute per user for regular endpoints.
  • 5 per minute for login and password reset, to stop brute-force attacks.
  • 429 responses with a Retry-After header, so well-behaved clients can wait politely instead of hammering away.

Documentation and error handling

An undocumented API costs you twice as much time on every integration. We generate OpenAPI specs automatically from the code, so the documentation never drifts away from reality. The biggest danger with hand-written docs is exactly that: they fall behind, a developer trusts them, and the integration fails in a way that takes hours to track down. With a current OpenAPI spec, an integrating developer can make their first call within an hour instead of guessing for half a day.

Error responses must be consistent. One shape, always. For example, an error object with a machine-readable code, a human-readable message, and optional details per field. Use HTTP status codes as intended: 400 for validation errors, 401 for missing auth, 403 for insufficient rights, 404 for non-existent resources, 422 for semantic validation. A 200 with an error message hidden in the body is a bug that forces every client to parse the body before it knows whether something succeeded.

Logging belongs here too. We log every server-side error with context: which user, which endpoint, a hash of the payload. That way a customer report is traceable without putting sensitive data in your logs. At Lexi and IndexNu, that discipline saves hours of debugging per incident, because you do not have to reconstruct what happened. You can simply read it back.

What this costs

A production-ready API as part of a SaaS platform sits at NedDev in the range of €15,000 to €60,000, depending on the number of domains, the complexity of authorization, and the number of external integrations. A standalone API layer on top of an existing system is considerably cheaper. The biggest cost saving is not in the build but in maintenance: an API that is versioned, documented, and consistent from the start costs a fraction in maintenance years later compared to one that grew organically. Want to know which approach fits your system? Take a look at our API development service or request an audit.

FREQUENTLY ASKED

Development · FAQ.

Is GraphQL better than REST?

No, it is different. GraphQL solves the problem of clients that need varying data shapes and would otherwise make many round-trips. For most CRUD platforms with stable resources, REST is simpler, easier to cache, and easier to secure. We start with REST by default and only migrate where GraphQL genuinely adds value.

How do you version an API without breaking existing clients?

Put the version in the URL, for example /api/v1/. When a breaking change is needed, you run v2 alongside v1 and give existing clients time to migrate with a clear deprecation date. That way old integrations keep working while new features become available.

Why rate-limiting from the start?

Without rate-limiting your API is vulnerable to brute-force and a single client can take down your server. Adding it after the fact means having to rein in clients already used to unlimited calls. Start with limits like 60 requests per minute and stricter limits on login endpoints.

NEED A HAND

Ready for your next build.

Book an intro → Direct line to the founder · M. Tufan