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.

25 minutes · Symfony

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

  1. Choose one resource, such as a product. Its collection can be /api/products and one product /api/products/{id}.
  2. Use GET for reading and POST for creating. Add updates and deletion only when you truly need them.
  3. For each endpoint, write down the input, successful response, possible errors, and permissions. That is your contract.
  4. Put the route on the controller with a #[Route] attribute. Start URLs with a shared /api prefix.
Official Symfony routing documentation

2. Separate input from the database model

  1. Do not let a client send data directly into an entity. Create a small input object for one request.
  2. Put only fields the client is allowed to send in it. Add validation rules for required values, length, and format.
  3. In the controller, read JSON, map it to the input object, and validate it. Only then call an application service.
  4. 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

  1. Return a successful response as JSON. Choose field names and do not change them without agreeing with clients.
  2. Use meaningful HTTP methods and statuses: 200 for reads, 201 after creation, and 404 for a missing resource.
  3. Return errors in the same shape. Include a short message and a stable code that a client can recognise.
  4. Do not expose sensitive internal exceptions or database errors. Write them to logs instead.
Official Symfony controller documentation

Step 4

Check the API as an ordinary client

Do not only check that the happy path works. Try situations that will actually happen.

  1. Check registered routes

    Symfony lists every route. For your endpoint, check the method, URL, and name.

    php bin/console debug:router
  2. 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
  3. 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.

Request a call

I will call you on the next working day between 9:00 and 17:00.

You can also call me directly.

+420 605 181 728

Leave your phone number and send a callback request.

By sending, you agree to processing your data in order to handle your request.