- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Building a REST API in Laravel 12 With OpenAPI Docs That Stay Accurate
About Post
The mobile developer messages you: "What does the employees endpoint return when the department is missing? And is it joined_at or join_date?" You check the wiki page. It says start_date. Nobody has used that name for a year.
An API without documentation gets used wrongly. An API with outdated documentation is worse, because people trust it. The fix is docs that are generated from, or live right next to, the code.
Let's build a small, clean REST API in Laravel 12, step by step, and then add OpenAPI (Swagger) docs in two different ways. The example is an employees resource, the kind you'd find in any HR system. My own personal Laravel 12 HR project documents its API with Swagger/OpenAPI, and it's one of those decisions that pays for itself the first time someone else has to call your endpoints.
Step 1: set up the API layer
A fresh Laravel 12 app doesn't include API routes by default. One command adds routes/api.php and installs Sanctum for token authentication:
php artisan install:api
Then the routes. apiResource registers the five standard endpoints (index, store, show, update, destroy) without the HTML-only create and edit:
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('employees', EmployeeController::class);
});
Run php artisan route:list --path=api and you'll see them, all prefixed with /api.
Step 2: validation in a Form Request
Keep validation out of the controller. A Form Request validates before your controller method even runs, and returns a 422 with field errors as JSON when the client sends Accept: application/json:
class StoreEmployeeRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:employees,email'],
'department_id' => ['nullable', 'exists:departments,id'],
'joined_at' => ['required', 'date'],
];
}
}
Step 3: shape the output with an API Resource
Never return a model directly. The day someone adds a sensitive column to the table, it's in your API. A Resource is an explicit contract of what leaves your app:
class EmployeeResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'department' => new DepartmentResource($this->whenLoaded('department')),
'joined_at' => $this->joined_at?->toDateString(),
];
}
}
whenLoaded only includes the department if you eager-loaded it, which quietly protects you from N+1 queries.
Step 4: a thin controller
class EmployeeController extends Controller
{
public function index(Request $request)
{
$perPage = min($request->integer('per_page', 15), 100);
return EmployeeResource::collection(
Employee::with('department')->latest()->paginate($perPage)
);
}
public function store(StoreEmployeeRequest $request)
{
return new EmployeeResource(Employee::create($request->validated()));
}
public function show(Employee $employee)
{
return new EmployeeResource($employee->load('department'));
}
}
A few things you get for free here. Paginated collections include links and meta automatically. A freshly created model returned through a Resource gets a 201 status without you setting it. And route model binding returns a 404 for a missing employee. Cap per_page like above, or someone will eventually ask for a million rows.
Step 5: make every error JSON
By default, Laravel renders errors as JSON when the request asks for JSON. Clients don't always send the right header, so in bootstrap/app.php make it unconditional for API routes:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->shouldRenderJsonWhen(
fn (Request $request, Throwable $e) => $request->is('api/*') || $request->expectsJson()
);
})
Step 6: OpenAPI docs, two ways
OpenAPI is a standard format for describing an API: its paths, parameters, request bodies and responses. "Swagger" is the older name, and Swagger UI is the interactive page most people picture, with a "Try it out" button on each endpoint. Two popular routes in Laravel:
Option A: describe it with attributes (swagger-php / L5-Swagger)
The darkaonline/l5-swagger package uses swagger-php, where you describe each endpoint with PHP attributes right above the method:
use OpenApi\Attributes as OA;
#[OA\Get(
path: '/api/employees/{employee}',
summary: 'Show one employee',
security: [['sanctum' => []]],
tags: ['Employees'],
parameters: [new OA\Parameter(name: 'employee', in: 'path', required: true,
schema: new OA\Schema(type: 'integer'))],
responses: [
new OA\Response(response: 200, description: 'The employee'),
new OA\Response(response: 404, description: 'Employee not found'),
],
)]
public function show(Employee $employee) { /* ... */ }
Generate the spec with php artisan l5-swagger:generate and the UI is served at /api/documentation by default. You also need one OA\Info attribute (title and version) somewhere in your app, plus a security scheme definition for the bearer token.
Pros: full control, very explicit. Cons: it's verbose, and because the attributes are written by hand, they can still drift from the code if nobody updates them.
Option B: infer it from the code (Scramble)
dedoc/scramble takes the opposite approach: it reads your routes, Form Request rules and Resource classes and builds the OpenAPI document automatically, with no annotations. Install it and the docs appear at /docs/api (by default only in the local environment).
Pros: close to zero effort, and it changes when the code changes. Cons: it can only document what it can infer, so complex responses sometimes need a type hint or a small annotation to come out right.
My rule of thumb: prefer docs that are generated from the code that actually runs. If you write them by hand, regenerate the spec in CI and fail the build if it's invalid, so "the docs are broken" is caught in a pull request instead of by a frustrated client developer.
Small things that make an API pleasant
- Consistent names. Pick snake_case or camelCase for JSON keys and never mix them.
- Dates as ISO 8601 strings, always in the same timezone convention.
- Version from day one (
/api/v1), even if you hope you'll never need v2. - Feature tests per endpoint that assert the JSON structure with
assertJsonStructure. They're your second, executable form of documentation.
Recap
Routes with apiResource, validation in Form Requests, output through Resources, JSON errors everywhere, and an OpenAPI spec that's generated rather than remembered. That's a REST API other people can actually use without messaging you.
Do you write your OpenAPI docs by hand, annotate the code, or generate them automatically? And what pushed you that way?

Be first to comment it...