
September 10, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
You need a REST API that mobile apps, partner systems, and your own frontend can rely on for years. The fastest way to get there is to design a REST API with OpenAPI and Swagger before you write route handlers, not after. OpenAPI 3.1 gives you a machine-readable contract. Swagger UI turns that contract into interactive docs your team can test on day one. On production Laravel applications I've shipped since 2010, this contract-first approach has prevented the drift that kills integrations. This guide walks through resource modelling, spec authoring, validation, and deployment using patterns from real API development projects.
What is the difference between OpenAPI and Swagger when you design a REST API?
OpenAPI is the specification format. Swagger is the tooling ecosystem built around it. Think of OpenAPI as the blueprint and Swagger as the workshop where you preview and test that blueprint.
Before 2016, "Swagger" referred to both the spec and the tools. The spec was donated to the Linux Foundation and renamed OpenAPI. Today, Swagger UI, Swagger Editor, and Swagger Codegen remain the most widely used interfaces for working with OpenAPI documents.
| Term | What it is | When you use it |
|---|---|---|
| OpenAPI 3.1 | JSON/YAML specification standard | Defining paths, schemas, security, examples |
| Swagger UI | Browser-based interactive docs | Manual testing, partner onboarding |
| Swagger Editor | Online or self-hosted spec editor | Drafting and linting specs |
| Redoc | Alternative doc renderer | Public developer portals with read-only docs |
For deeper spec syntax, see the OpenAPI 3.1 specification guide. For choosing between Swagger UI and Redoc in production, read API documentation with Redoc and Swagger UI.
How do you model REST resources before writing an OpenAPI spec?
Start with nouns, not endpoints. A booking portal needs bookings, clients, and documents. Each resource gets a plural URI segment and standard HTTP verbs mapped to intent.
Map HTTP verbs to business actions
- GET /bookings — list with pagination and filters
- GET /bookings/{id} — fetch one record
- POST /bookings — create; return 201 with Location header
- PUT /bookings/{id} — full replace
- PATCH /bookings/{id} — partial update
- DELETE /bookings/{id} — remove or soft-delete
Follow the same resource rules outlined in REST API design best practices for 2026. Avoid verbs in URLs like /createBooking. Use query parameters for filtering, not new path segments.
Version your API in the path
Prefix every route with /api/v1/. When breaking changes arrive, ship /api/v2/ and keep v1 running through a sunset window. I've used this pattern on client portals where mobile apps cannot force-update overnight. See Laravel API versioning strategy for migration tactics.
How do you write an OpenAPI 3.1 specification for a REST API?
Place your spec at openapi/openapi.yaml in the repo root. Use YAML for readability. The official OpenAPI 3.1 specification is the authoritative reference for every field.
Minimal working spec for a bookings API
openapi: 3.1.0
info:
title: Client Portal Bookings API
version: 1.0.0
description: REST API for appointment booking and document uploads.
servers:
- url: https://api.example.com/api/v1
description: Production
- url: http://localhost:8000/api/v1
description: Local (Laravel 13 + PHP 8.3+)
paths:
/bookings:
get:
summary: List bookings
operationId: listBookings
tags: [Bookings]
parameters:
- name: page
in: query
schema: { type: integer, minimum: 1, default: 1 }
- name: status
in: query
schema: { type: string, enum: [pending, confirmed, cancelled] }
responses:
'200':
description: Paginated booking list
content:
application/json:
schema:
$ref: '#/components/schemas/BookingCollection'
'401':
$ref: '#/components/responses/Unauthorized'
post:
summary: Create a booking
operationId: createBooking
tags: [Bookings]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BookingCreate'
responses:
'201':
description: Booking created
headers:
Location:
schema: { type: string, format: uri }
content:
application/json:
schema:
$ref: '#/components/schemas/Booking'
'422':
$ref: '#/components/responses/ValidationError'
/bookings/{bookingId}:
get:
summary: Get one booking
operationId: getBooking
tags: [Bookings]
parameters:
- $ref: '#/components/parameters/BookingId'
responses:
'200':
description: Single booking
content:
application/json:
schema:
$ref: '#/components/schemas/Booking'
'404':
$ref: '#/components/responses/NotFound'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
parameters:
BookingId:
name: bookingId
in: path
required: true
schema: { type: string, format: uuid }
schemas:
Booking:
type: object
required: [id, client_name, scheduled_at, status]
properties:
id: { type: string, format: uuid }
client_name: { type: string, maxLength: 120 }
scheduled_at: { type: string, format: date-time }
status: { type: string, enum: [pending, confirmed, cancelled] }
BookingCreate:
type: object
required: [client_name, scheduled_at]
properties:
client_name: { type: string, maxLength: 120 }
scheduled_at: { type: string, format: date-time }
BookingCollection:
type: object
required: [data, meta]
properties:
data:
type: array
items: { $ref: '#/components/schemas/Booking' }
meta:
type: object
properties:
current_page: { type: integer }
last_page: { type: integer }
total: { type: integer }
Error:
type: object
required: [message]
properties:
message: { type: string }
errors:
type: object
additionalProperties:
type: array
items: { type: string }
responses:
Unauthorized:
description: Missing or invalid token
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
NotFound:
description: Resource not found
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
ValidationError:
description: Validation failed
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
security:
- bearerAuth: []
Paste the YAML into the JSON formatter when you need to convert between YAML and JSON for CI tools. Keep schemas in components/schemas and reference them with $ref to avoid duplication.
How do you serve Swagger UI and validate requests against the OpenAPI contract?
Swagger UI renders your spec as browsable docs with a Try-it-out button. Request validation ensures incoming traffic matches the contract before your controller runs. Both steps close the gap between documentation and behaviour.
Install Swagger UI in a Laravel 13 project
Laravel 13 requires PHP 8.3 or higher. Laravel 12 runs on PHP 8.2+. Add a lightweight package or serve static Swagger UI assets from public/docs.
- Copy the official Swagger documentation bundle into
public/swagger-ui/. - Create
resources/views/docs/swagger.blade.phpthat loads Swagger UI and points at/openapi.yaml. - Expose the spec via a read-only route or symlink from
public/openapi.yaml. - Protect staging docs with HTTP basic auth. Keep production docs public only when the API is partner-facing.
<!-- resources/views/docs/swagger.blade.php -->
<link rel="stylesheet" href="/swagger-ui/swagger-ui.css">
<div id="swagger-ui"></div>
<script src="/swagger-ui/swagger-ui-bundle.js"></script>
<script>
SwaggerUIBundle({
url: '/openapi.yaml',
dom_id: '#swagger-ui',
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis],
});
</script>
Validate requests with middleware
Use league/openapi-psr7-validator or generate Laravel Form Request classes from the spec. On a legal-tech portal I built, OpenAPI-driven validation caught malformed document-upload payloads before they hit storage logic.
composer require league/openapi-psr7-validator
/* app/Http/Middleware/ValidateOpenApi.php */
public function handle(Request $request, Closure $next)
{
$validator = new ValidatorBuilder()
->fromYamlFile(base_path('openapi/openapi.yaml'))
->getRequestValidator();
$validator->validate(
$request->method(),
$request->getPathInfo(),
(string) $request->getContent()
);
return $next($request);
}
Pair validation with Sanctum token auth as described in building a REST API with Laravel Sanctum authentication. Apply the middleware only to /api/* routes so web sessions stay untouched.
What security and error patterns belong in every OpenAPI REST API design?
A spec without security schemes is incomplete. Document auth, rate limits, and error envelopes in the same file your partners read.
Security schemes to declare
- Bearer tokens — Sanctum or JWT for mobile and SPA clients
- API keys — server-to-server integrations with scoped keys
- OAuth 2.0 — third-party apps needing delegated access
Cross-check your design against the API security complete checklist. Never expose internal IDs sequentially if the resource is sensitive. UUIDs reduce enumeration risk on client portals.
Standardise error responses
Every 4xx and 5xx response should share one schema. Laravel's default validation JSON already fits the Error schema above. Document 401, 403, 404, 422, and 429 in components/responses so Swagger UI shows them per endpoint.
For write operations that partners may retry, add idempotency key support. The header belongs in your OpenAPI parameters section. Read API idempotency keys implementation guide for header naming and storage patterns.
How do you keep OpenAPI specs and Laravel code in sync during CI/CD?
Specs drift unless CI enforces them. Treat the OpenAPI file as source code reviewed in every pull request. This is the core of API-first development workflow.
Lint the spec on every commit
npm install -g @stoplight/spectral-cli
spectral lint openapi/openapi.yaml --ruleset .spectral.yaml
Add rules that require operationId, response examples, and description fields. Spectral catches incomplete endpoints before merge.
Run contract tests against a running app
Spin up Laravel in CI, hit each documented path, and compare responses to the schema. Tools like Dredd or Schemathesis read your OpenAPI file and fuzz inputs automatically. For consumer-driven checks, see API contract testing with Pact.
Wire Laravel routes to match the spec
/* routes/api.php — Laravel 13 */
Route::prefix('v1')->middleware(['auth:sanctum', 'openapi.validate'])->group(function () {
Route::get('/bookings', [BookingController::class, 'index']);
Route::post('/bookings', [BookingController::class, 'store']);
Route::get('/bookings/{booking}', [BookingController::class, 'show']);
Route::patch('/bookings/{booking}', [BookingController::class, 'update']);
Route::delete('/bookings/{booking}', [BookingController::class, 'destroy']);
});
Follow Laravel routing documentation for route model binding and middleware groups. Align controller return types with your OpenAPI response schemas. Use API Resources or dedicated DTO serializers so JSON shape stays predictable.
On projects using GitLab CI and Deployer 7, I publish the spec artifact alongside each release. Partners always see docs that match the deployed build. The same pipeline pattern runs on sister legal-tech sites sharing shared EC2 infrastructure.
Generate client SDKs when partners need them
OpenAPI Generator can produce PHP, JavaScript, or mobile client stubs from the same YAML file. If you publish a public API, document SDK usage in a separate guide. See SDK design for your public API for naming and versioning rules.
For webhook callbacks documented alongside REST endpoints, apply the same schema discipline. Event payloads deserve their own schemas under components. Read webhook design patterns for reliability before adding POST /webhooks routes.
A portal like Mijar Law Associates combines REST endpoints for bookings, document uploads, and payment status. Each integration point started as an OpenAPI path review with the client's IT team before a line of PHP shipped.
Key Takeaways
- Write the OpenAPI 3.1 YAML spec before Laravel routes; treat it as the single source of truth for paths, schemas, and errors.
- Serve Swagger UI from a dedicated docs route and protect staging environments with basic auth.
- Validate every API request against the spec in middleware so malformed JSON never reaches business logic.
- Version paths with
/api/v1/, document security schemes, and standardise error envelopes across all endpoints. - Run Spectral lint and contract tests in CI to block merges when code and spec diverge.
- Generate SDKs from the same spec when partners need typed clients instead of raw HTTP calls.
People Also Ask
Can you use OpenAPI with Laravel without Swagger annotations?
Yes. A standalone openapi.yaml file is often cleaner than scattering annotations in controllers. You load the file into Swagger UI and validate requests with middleware. Annotations work for code-first teams, but they couple documentation to implementation details.
Should new REST APIs use OpenAPI 3.0 or 3.1?
Start new projects on OpenAPI 3.1. It aligns JSON Schema draft 2020-12 and handles nullable types more cleanly. Most Swagger UI versions released after 2023 support 3.1 without issues.
How do you document pagination in OpenAPI?
Define a reusable meta object in components/schemas with current_page, last_page, and total fields. Reference it in list endpoint responses. Document query parameters page and per_page on each collection GET operation.
Is Swagger UI safe to expose in production?
Public partner APIs benefit from public docs. Internal admin APIs should sit behind authentication or VPN access. Never expose Try-it-out against production data without rate limiting and scoped tokens.
Ship APIs your partners can trust
The teams that integrate fastest are the ones who receive a tested OpenAPI file on day one, not a PDF emailed after launch. When you design a REST API with OpenAPI and Swagger as the contract layer, documentation, validation, and client SDKs stay aligned through every release. Start with resource modelling, commit the YAML spec, wire Swagger UI, and enforce the contract in CI.
Need help designing a production API for a portal, eCommerce backend, or mobile companion app? Review how to build a REST API in Laravel the right way and Laravel API best practices, then contact us for architecture and implementation support through custom software development.
Frequently Asked Questions
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.

