Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

REST API Design Best Practices

By Kokil Thapa | Last reviewed: September 2026

REST API design best practices matter the moment your mobile app, partner integration, or internal dashboard depends on stable endpoints. A poorly shaped API forces every consumer to guess field meanings, retry failed writes, and patch around breaking URL changes. On production Laravel applications I maintain, the difference between a calm integration and a weekly fire drill usually traces back to decisions made before the first route shipped. This guide walks through the patterns that survive real traffic, real teams, and real API development projects in Nepal and abroad.

What Are the Core Principles of REST API Design Best Practices?

REST is not a framework. It is a set of constraints: stateless requests, cacheable responses where possible, a uniform interface, and resources identified by URLs. Your job is to make those resources predictable for humans and machines.

Think in nouns, not verbs. A booking portal exposes /bookings, not /createBooking. Actions that do not map cleanly to CRUD belong on sub-resources: POST /bookings/{id}/cancel rather than POST /cancelBooking. I've seen legal-tech portals accumulate dozens of RPC-style endpoints; each one becomes a special case for documentation and testing.

REST Resource ModelCollectionGET /bookingsItemGET /bookings/42Sub-resourcePOST .../cancelHTTP Verbs on One ResourceGET readPOST createPUT replaceDELETEPATCH for partial updates onlyStateless: every request carries auth + context
REST API design best practices start with collections, items, and sub-resources mapped to standard HTTP verbs.

Plural collection names reduce ambiguity. Use /users and /users/{id}, never mixed singular and plural paths. Keep identifiers opaque—UUIDs or bigint IDs—not sequential guessable integers when the resource is sensitive. For public directories like lawyer listings, sequential IDs are often fine.

JSON is the default representation in 2026. Pick camelCase or snake_case and apply it everywhere. Laravel tends toward snake_case in Eloquent; many mobile clients expect camelCase. Transform at the boundary with API Resources rather than renaming columns ad hoc. That pattern is covered in depth in our Laravel API best practices guide.

URL design checklist

  1. Use HTTPS only; reject plain HTTP at the edge.
  2. Prefix with a version segment: /api/v1/bookings.
  3. Filter with query params: ?status=confirmed&date_from=2026-01-01.
  4. Never expose internal database table names in URLs.
  5. Limit nesting to two levels: /bookings/{id}/documents is enough.

How Should You Structure HTTP Status Codes and Error Responses?

Status codes are part of your contract. Clients branch on them. Returning 200 OK with an error object in the body breaks every HTTP-aware library and retry policy.

Use the codes defined in RFC 9110 consistently. 201 Created on POST with a Location header. 204 No Content on DELETE when the body is empty. 404 Not Found when the resource ID does not exist for that tenant. 409 Conflict when a unique constraint fails. 422 Unprocessable Content for validation errors—Laravel's Form Request layer maps naturally here.

Errors should be machine-readable and human-debuggable. A practical envelope:

{
  "error": {
    "code": "BOOKING_DATE_UNAVAILABLE",
    "message": "The selected date is fully booked.",
    "details": [
      { "field": "date", "issue": "no_slots_remaining" }
    ],
    "request_id": "req_8f3k2m9x"
  }
}

Log the request_id server-side and return it in every error. Support teams can grep one ID instead of reconstructing a trace from timestamps. Tie this into structured logging best practices so correlation IDs flow from API gateway through queue workers.

Do not leak stack traces or SQL fragments in production responses. Map exceptions to safe codes in your exception handler. For Laravel 12 or 13 applications, centralise mapping in bootstrap/app.php or a dedicated ApiExceptionRenderer.

StatusWhen to useCommon mistake
200Successful GET, PUT, PATCH with bodyUsing 200 for failed business logic
201Resource created via POSTOmitting Location header
204Successful DELETE or action with no bodyReturning empty JSON with 200
400Malformed JSON or unknown query paramUsing for validation (prefer 422)
401Missing or invalid authenticationConfusing with 403
403Authenticated but not authorisedReturning 404 to hide existence
404Resource not foundUsing for wrong HTTP method
409Duplicate or state conflictRetrying without idempotency key
422Validation failedInconsistent field naming in details
429Rate limit exceededNo Retry-After header
500Unexpected server faultExposing internal exception text

How Do You Implement Pagination, Filtering, and Sorting Correctly?

List endpoints are where APIs die under load. Returning ten thousand rows as a default guarantees timeouts and angry mobile users. Pagination is not optional on any collection that can grow.

Offset pagination is simple: ?page=2&per_page=25. It works for admin tables and small datasets. Cursor pagination scales better: ?cursor=eyJpZCI6MTIzfQ&limit=25. Cursors stay stable when rows are inserted during iteration. Document which fields the cursor encodes.

Pagination Strategy ChoiceOffset Pagination?page=3&per_page=25Easy for UI page numbersSlow on deep offsetsDuplicate rows on insertCursor Pagination?cursor=abc&limit=25Stable under writesIndex-friendly queriesBest for feeds and syncReturn meta: total, next_cursor, per_page
REST API design best practices favour cursor pagination for high-churn lists and offset pagination for small admin views.

Wrap list responses in a consistent envelope:

{
  "data": [ { "id": "bk_01", "status": "confirmed" } ],
  "meta": {
    "per_page": 25,
    "next_cursor": "eyJpZCI6NTAwfQ",
    "has_more": true
  }
}

Filtering belongs in query strings, not POST bodies, for idempotent reads. Whitelist allowed filter keys in validation. Reject unknown filters with 400 instead of silently ignoring them—silent ignores hide client bugs for weeks.

Sorting: ?sort=-created_at,name. Document allowed sort fields. Never pass raw column names from the client into SQL without a mapping table. That is an injection vector even with an ORM.

On eCommerce APIs I've worked on, product filters combine category, price range, and stock status. Encode each as explicit query params and index the matching composite keys in MySQL 8.4 or PostgreSQL 18. Validate payloads during development with a JSON formatter and validator before they hit integration tests.

What Is the Right Way to Version and Document a REST API?

Every public API will change. Versioning is how you change without breaking paying integrators. The two sane options are URL prefix versioning (/v1/, /v2/) and header negotiation (Accept: application/vnd.myapp.v2+json). URL versioning is easier for partners and browser debugging. Header versioning keeps URLs clean but confuses caches.

Pick one strategy and document it in your Laravel API versioning strategy. Never version individual fields inside one URL—that becomes unmaintainable fast. Ship breaking changes only in a new major version. Run old and new versions in parallel for a published sunset window.

OpenAPI 3.1 is the documentation standard integrators expect in 2026. Maintain a single openapi.yaml as source of truth or generate it from code with Scribe or similar tools. Our walkthrough on designing a REST API with OpenAPI and Swagger shows the workflow end to end.

API Contract LifecycleDesignResourcesOpenAPIopenapi.yamlImplementLaravel 13TestContract testsVersion Sunset Pathv1 activev2 parallelv1 sunsetPublish deprecation dates in docs and response headers
OpenAPI-first design and explicit version sunset windows are central REST API design best practices for long-lived products.

Include worked examples for every endpoint: request, response, and error cases. Link to authentication docs from the same page. Laravel Scribe can generate this from annotations; see API documentation with Scribe for Laravel for a production-ready setup on PHP 8.3 or 8.5.

Deprecation headers

When an endpoint nears retirement, send:

Deprecation: true
Sunset: Sat, 01 Mar 2027 00:00:00 GMT
Link: <https://api.example.com/docs/v2/migration>; rel="successor-version"

Pair headers with email notices to registered API consumers. The API deprecation and sunset best practices article covers timelines that actually work with small teams.

How Do You Secure REST APIs and Handle Writes Safely?

Security is part of design, not a late hardening pass. Authenticate every non-public route. Use OAuth 2.1 or token schemes appropriate to your client type. For first-party SPAs and mobile apps on Laravel, Sanctum authentication remains a practical default. For third-party integrations, scoped API keys or OAuth client credentials with narrow permissions.

Authorise at the resource level, not only the route. A user with a valid token must not fetch another tenant's booking by incrementing an ID. Policy classes and query scoping (where('organisation_id', $orgId)) belong in every list and show action.

Rate limiting protects your database and your bill. Return 429 with Retry-After. Separate limits for read and write. Stricter caps on auth and password-reset endpoints. Gateways like Kong or Traefik can enforce limits before PHP-FPM; see API gateways for microservices when traffic grows past a single server.

Secure Request PipelineClientMobile / SPATLS + GatewayRate limitAuth LayerOAuth / SanctumLaravel APIPolicy checkWrite Safety ControlsIdempotency-KeyValidationAudit logHTTPS only — reject mixed content and token leakage
REST API design best practices layer TLS, rate limits, authentication, and idempotent writes before business logic runs.

Idempotency keys matter for POST operations that create bills or bookings. Accept Idempotency-Key: uuid on the header. Store the key with the created resource hash for 24 hours. Replay the same response on duplicate submits instead of double-charging. Payment integrations for eSewa, Khalti, or Stripe all expect this discipline.

Validate on the server always. Client-side checks improve UX; they do not protect data. Use Form Requests in Laravel with explicit rules and custom messages. Return field-level errors in the 422 envelope.

Cross-check your surface against the API security complete checklist and OAuth security best practices before opening an API to partners.

How Should Laravel Projects Apply REST API Design Best Practices in Code?

Laravel 12 and 13 give you routing, Form Requests, API Resources, policies, and queues out of the box. The framework does not force good design—you still choose URL shapes and response contracts.

A minimal v1 route group:

Route::prefix('api/v1')
    ->middleware(['auth:sanctum', 'throttle:api'])
    ->group(function () {
        Route::apiResource('bookings', BookingController::class)
            ->only(['index', 'show', 'store', 'update']);
        Route::post('bookings/{booking}/cancel', [BookingController::class, 'cancel']);
    });

Transform models with API Resources so internal relations never leak accidentally:

class BookingResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->uuid,
            'status' => $this->status,
            'date' => $this->date->toDateString(),
            'created_at' => $this->created_at->toIso8601String(),
        ];
    }
}

Use ISO 8601 timestamps in UTC unless the domain requires local offsets. Document timezone behaviour in OpenAPI. For Nepal-facing apps, business dates may follow Bikram Sambat in the UI while the API stores Gregorian ISO dates internally.

Queue slow side effects. Creating a booking might enqueue confirmation email and SMS. Return 201 with the booking resource immediately; do not block on third-party SMS gateways. Webhook delivery to partners belongs in a queued job with retries and exponential backoff—patterns covered in webhooks design and security.

On a client portal like Mijar Law Associates, document uploads and payment status endpoints must stay stable across mobile and web clients. That is why I standardise envelopes and versioning before feature velocity accelerates.

For greenfield APIs, read how to build a REST API in Laravel the right way alongside the official Laravel 13 routing documentation. If you are choosing between REST and GraphQL for a new product, the comparison in REST vs GraphQL vs gRPC saves weeks of wrong bets.

Contract tests catch drift between OpenAPI and implementation. Tools like Pact fit teams with multiple services; see API contract testing with Pact. Feature tests in Laravel should assert status codes and JSON shape, not only HTTP 200.

When you publish an SDK for partners, keep it thin—a typed wrapper over your OpenAPI spec. Over-customising client libraries creates a second codebase to maintain. The SDK design for your public API guide outlines what belongs in generated clients versus server-side logic.

Key Takeaways

  • Name resources with plural nouns, limit nesting, and map actions to HTTP verbs instead of verb-heavy URLs.
  • Return precise status codes and structured error objects with a request_id for every failure path.
  • Paginate every growing collection; prefer cursors for feeds and offset only for shallow admin lists.
  • Version in the URL or Accept header, document with OpenAPI 3.1, and sunset old versions with explicit dates.
  • Authenticate, authorise, rate-limit, and use idempotency keys on write endpoints that must not double-run.
  • In Laravel, enforce contracts with API Resources, Form Requests, policies, and automated contract tests.

People Also Ask

What is the difference between REST and RESTful APIs?

REST describes architectural constraints—statelessness, uniform interface, cacheability. RESTful describes an API that follows those constraints in practice. Many products labelled RESTful are really HTTP+JSON RPC with resource-ish URLs. Aim for true resource orientation and correct verb semantics rather than the label alone.

Should REST APIs use PUT or PATCH for updates?

Use PUT when the client sends a full replacement representation of the resource. Use PATCH for partial updates. Document which fields are required on PUT. In Laravel, both map to controller update methods; validate accordingly so PATCH cannot null out fields the client omitted.

How do you handle file uploads in a REST API?

Prefer a two-step flow: POST to obtain a signed upload URL or upload ticket, then PUT the binary to object storage, then PATCH the resource with the file reference. Multipart POST works for small files but scales poorly through PHP-FPM. Return metadata—MIME type, size, checksum—not raw paths on disk.

Is HATEOAS required for REST API design best practices?

HATEOAS—hypermedia links in responses—is part of REST theory but rarely used in mobile and SPA clients today. Practical APIs include pagination links (next, prev) and occasional action URLs. Full hypermedia discovery is optional unless your integrators explicitly want it.

Ship APIs Your Integrators Can Trust

REST API design best practices are not academic. They are the difference between an integration that ships in a week and one that stalls on undocumented edge cases. Resource clarity, honest status codes, pagination, versioning, OpenAPI docs, and write safety are the pillars I apply on every custom software and API project. If you are planning a partner-facing API, a mobile backend, or a migration off RPC-style endpoints, contact us to review your contract before code hardens around bad shapes. Related reading: building RESTful APIs with Laravel and the Quick And Easy Nepalese Grocery Laravel eCommerce API patterns in our portfolio.

Frequently Asked Questions

Resource-oriented URLs, correct HTTP verbs and status codes, consistent JSON envelopes, cursor or offset pagination, explicit versioning, idempotent writes, and machine-readable OpenAPI docs—so clients integrate once and keep working through upgrades.

REST is a set of constraints, not a framework: stateless requests, cacheable responses where possible, a uniform interface, and resources identified by URLs. Think in nouns, not verbs—expose /bookings, not /createBooking. Actions that do not map cleanly to CRUD belong on sub-resources, such as POST /bookings/{id}/cancel. Use plural collection names like /users and /users/{id}, keep identifiers opaque for sensitive resources, pick camelCase or snake_case consistently, and transform at the boundary with API Resources rather than renaming database columns ad hoc.

Return 422 Unprocessable Content. Laravel Form Request validation maps naturally to this code. Do not use 400 for field-level validation failures or return 200 OK with an error object in the body—that breaks HTTP-aware libraries and client retry policies.

Status codes are part of your contract—clients branch on them. Use RFC 9110 codes consistently: 201 Created on POST with a Location header, 204 No Content on DELETE, 404 when a resource ID does not exist, 409 on duplicate or state conflict, 422 for validation errors, 429 when rate limited with a Retry-After header. Return a machine-readable error envelope with a stable code, human message, field-level details, and a request_id logged server-side. Never leak stack traces or SQL fragments in production—map exceptions to safe responses in your exception handler.

Paginate every collection that can grow. Offset pagination (?page=2&per_page=25) suits small admin tables; cursor pagination (?cursor=eyJpZCI6MTIzfQ&limit=25) scales better for high-churn feeds because cursors stay stable when rows are inserted during iteration. Wrap lists in a consistent envelope with data and meta fields including next_cursor and has_more. Put filtering in query strings, whitelist allowed filter keys, and reject unknown filters with 400. For sorting, document allowed fields like ?sort=-created_at,name and never pass raw client column names into SQL without a mapping table.

Prefer cursor pagination for high-churn lists where rows are inserted or deleted while clients iterate—feeds, activity logs, and large product catalogs. Offset pagination works for shallow admin views and small datasets where page numbers matter more than stability under concurrent writes. Document which fields each cursor encodes so integrators know what they are paging against.

Every public API will change, so pick one versioning strategy and stick with it: URL prefix versioning (/api/v1/bookings) is easier for partners and browser debugging; header negotiation (Accept: application/vnd.myapp.v2+json) keeps URLs clean but confuses caches. Ship breaking changes only in a new major version and run old and new versions in parallel during a published sunset window. Document with OpenAPI 3.1 as the source of truth—include worked examples for every endpoint. When retiring endpoints, send Deprecation, Sunset, and Link successor-version headers paired with email notices to registered consumers.

Authenticate every non-public route using OAuth 2.1, Sanctum for first-party SPAs and mobile apps, or scoped API keys for third-party integrations. Authorise at the resource level—valid tokens must not access another tenant's data by incrementing an ID. Apply rate limits with 429 and Retry-After, with stricter caps on auth endpoints. Accept Idempotency-Key headers on POST operations that create bills or bookings, store the key for 24 hours, and replay the same response on duplicate submits. Validate on the server always with Form Requests and return field-level 422 errors.

Laravel 12 and 13 provide routing, Form Requests, API Resources, policies, and queues—but you still choose URL shapes and response contracts. Group routes under api/v1 with auth:sanctum and throttle middleware, use apiResource for standard CRUD, and map non-CRUD actions to sub-resources like bookings/{booking}/cancel. Transform models with API Resources so internal relations never leak. Use ISO 8601 timestamps in UTC, queue slow side effects like email and SMS instead of blocking the response, and write feature tests that assert status codes and JSON shape—not only HTTP 200.

REST describes architectural constraints—statelessness, uniform interface, cacheability. RESTful describes an API that follows those constraints in practice. Many products labelled RESTful are really HTTP-plus-JSON RPC with resource-ish URLs. The label matters less than true resource orientation: plural nouns in URLs, correct verb semantics, predictable status codes, and consistent response envelopes that survive real integrator traffic.

Use PUT when the client sends a full replacement representation of the resource and document which fields are required. Use PATCH for partial updates where omitted fields should remain unchanged. In Laravel, both map to controller update methods—validate accordingly so a PATCH request cannot null out fields the client did not intend to touch. Document the behaviour in OpenAPI so mobile and partner clients know which verb to call.

Prefer a two-step flow: POST to obtain a signed upload URL or upload ticket, PUT the binary to object storage, then PATCH the resource with the file reference. Multipart POST works for small files but scales poorly through PHP-FPM because it ties up worker processes. Return metadata—MIME type, size, checksum—not raw filesystem paths on disk, and keep upload endpoints separate from standard resource CRUD routes.

Limit nesting to two levels. /bookings/{id}/documents is enough; deeper paths become brittle when relationships change and harder for partners to construct correctly. Never expose internal database table names in URLs, prefix routes with a version segment like /api/v1/, filter with query params rather than path segments, and use HTTPS only—reject plain HTTP at the edge.

HATEOAS—hypermedia links embedded in responses—is part of the original REST constraint set but rarely required in practice for mobile apps, partner integrations, or internal dashboards. Most production APIs in 2026 succeed with stable resource URLs, OpenAPI 3.1 documentation, and explicit versioning instead. Add hypermedia links only when clients genuinely discover actions dynamically; otherwise invest effort in clear contracts, consistent envelopes, and deprecation headers that integrators can act on.

Returning 200 OK with errors in the body, mixing singular and plural paths, silently ignoring unknown filter params, skipping pagination on growing collections, versioning individual fields inside one URL, exposing stack traces in error responses, and blocking POST responses on slow third-party calls like SMS gateways. On legal-tech and booking portals I maintain, these decisions made before the first route shipped determine whether integrations stay calm or become weekly fire drills. Standardise envelopes, request_id tracing, and contract tests early.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: