Design choices inside a Laravel API
Resources are the contract
We never return an Eloquent model straight from a controller. Each response goes through an API resource class that lists its fields one by one. That gives a single file to review when the contract changes, keeps internal columns private by default, and lets us load relationships only when the client asks for them. Lists are paginated, with cursor pagination where the data set is large or changes quickly. Errors follow one structure, with a machine-readable code beside the human message, so client developers write their error handling once.
Choosing between Sanctum and Passport
The question is who is calling. For your own mobile app, Sanctum issues personal access tokens, one per device, each with a list of abilities and each revocable on its own. For your own SPA on a related domain, Sanctum uses the normal session cookie, which keeps tokens out of browser storage. Passport is the choice when outside developers build on your platform and users must grant them access, because that needs real OAuth2 flows with client credentials, authorization codes and refresh tokens. Running Passport where Sanctum would do adds moving parts you then have to maintain.
Versioning you can live with
We version in the URL path because it is visible in logs and easy for client developers to reason about. A new version is created only for breaking changes. Adding a field is not one. Removing or renaming a field is. Older versions keep their own resource classes over the same underlying actions, so a bug fix in business logic reaches every version at once. We log which versions and which app builds are still calling, which turns "can we remove v1 yet?" into a query instead of an argument.
Limits, validation and abuse
Rate limits are defined as named limiters and can differ by endpoint and by customer plan. Login, registration and password reset get tight limits keyed on both account and IP address. Form requests validate every input and run the policy check before the controller body executes. Endpoints that create something accept an idempotency key where clients may retry on a weak connection, so a double tap does not become a double order.
Docs and tests from the same source
The OpenAPI document is generated from the code and published on every release. Feature tests call each endpoint as each role, assert status codes and JSON structure, and fail the build if a response stops matching the specification. If your consumer is a Flutter app, see how we pair the two on our Laravel backend for Flutter page.