Practical guide
How to design a REST API in Symfony
Start with the contract, not the controller. It makes the API easier to use, test, and extend.
First, the short version
What are you building?
An API is an agreement for passing data between two applications. A REST API usually builds that agreement on HTTP: the URL describes a resource, the method describes an action, and the response carries the result.
An endpoint is one specific API address, such as GET /api/products/42. Keep both input and output simple, most often using JSON.
Get ready
What you need
A working Symfony project is enough. Before writing code, put one small use case on paper.
- A Symfony project and a local environment where you can run php bin/console.
- One specific resource, such as a product or an order. Do not start with the whole shop.
- An idea of who calls the API and what they are allowed to do. Add access control and identity checks early, not after launch.
- A tool for calling the API: curl, Postman, or Insomnia is enough to start.
Steps 1 to 3
Design the API in small pieces
Describe behaviour first. The code then only fulfils a clear agreement.
1. Describe resources and endpoints
- Choose one resource, such as a product. Its collection can be /api/products and one product /api/products/{id}.
- Use GET for reading and POST for creating. Add updates and deletion only when you truly need them.
- For each endpoint, write down the input, successful response, possible errors, and permissions. That is your contract.
- Put the route on the controller with a #[Route] attribute. Start URLs with a shared /api prefix.
2. Separate input from the database model
- Do not let a client send data directly into an entity. Create a small input object for one request.
- Put only fields the client is allowed to send in it. Add validation rules for required values, length, and format.
- In the controller, read JSON, map it to the input object, and validate it. Only then call an application service.
- Return invalid input with a 400 or 422 status and fields the caller needs to correct.
composer require symfony/validator symfony/serializer Official Symfony validation documentation 3. Return consistent responses
- Return a successful response as JSON. Choose field names and do not change them without agreeing with clients.
- Use meaningful HTTP methods and statuses: 200 for reads, 201 after creation, and 404 for a missing resource.
- Return errors in the same shape. Include a short message and a stable code that a client can recognise.
- Do not expose sensitive internal exceptions or database errors. Write them to logs instead.
Step 4
Check the API as an ordinary client
Do not only check that the happy path works. Try situations that will actually happen.
-
Check registered routes
Symfony lists every route. For your endpoint, check the method, URL, and name.
php bin/console debug:router -
Send one valid request
Replace the address with your local URL. The response should have the expected JSON and HTTP status.
curl -i http://localhost:8000/api/products/42 -
Try invalid input
Send a missing or invalid field. The API must not crash or return an HTML error page.
curl -i -X POST http://localhost:8000/api/products -H 'Content-Type: application/json' -d '{}'
If something goes wrong
Common problems
The endpoint returns HTML instead of JSON
The request probably did not reach the right controller, or the default error handler caught an exception. Check the route, the Accept header, and consistent error handling.
php bin/console debug:router A client can change a field it must not change
Do not map an entire request straight into an entity. Keep the input object small and copy only explicitly allowed fields into the entity.
An API error has a different shape each time
Standardise error responses in one place. A client then does not need to guess whether to read message, errors, or HTML.
You add API versioning too early
First keep a stable contract and make small changes backwards-compatible. Add a version in the URL only when you truly need two incompatible shapes side by side.
Done
You have a solid API foundation.
Your REST API now rests on a clear contract. Add the next endpoints using the same rhythm: describe, validate, perform the action, and return an understandable result.