DEV Community

Cover image for Building Powerful APIs in Symfony: A Complete Guide for Modern Frontend Integration

Building Powerful APIs in Symfony: A Complete Guide for Modern Frontend Integration

Building Powerful APIs in Symfony: A Complete Guide for Modern Frontend Integration

Index

  1. Introduction
  2. Understanding Symfony API Architecture
  3. Core Components for API Development
  4. Setting Up Your Symfony API Project
    • Step 1: Installation:
    • Step 2 : Creating the API Endpoint to Display Products Data
    • Step 3 : Creating the React Application
    • Now, To Create the React app (Vite)
    • Add a simple Products component
    • Use the component in src/App.jsx
    • Avoiding CORS issues with a dev proxy
  5. Stats
  6. Key Takeaways
  7. Interesting Facts
  8. FAQs
  9. Conclusion

Introduction

API-first programming is becoming more and more popular in modern apps. Having a neat, organized, and secure backend is essential whether you're using React, Vue, or another frontend framework.

This is easier than you might imagine with Symfony. We'll walk through the process of creating REST APIs in Symfony and use them in a basic front-end application in this blog.

“The journey of a thousand miles begins with a single step.” - Lao Tzu

Understanding Symfony API Architecture

Symfony is a great option for developing APIs because of its architecture. Symfony excels at developing headless backends that just use JSON APIs for communication, in contrast to conventional MVC frameworks that mostly support HTML pages.

"Symfony's component-based architecture allows developers to build APIs that are both powerful and maintainable, making it the perfect backend choice for modern JavaScript frameworks." - Fabien Potencier, Symfony Creator

Core Components for API Development
Controllers : Handle HTTP requests and return JSON responses.
Routes & Attributes : Define clear API paths using route annotations.
Serializer : Convert PHP arrays or objects into JSON for frontend consumption.
HTTP Foundation : Manage requests and responses effectively.
Security Layer : Optional for protected APIs, handling authentication and authorization.

Setting Up Your Symfony API Project

Step 1: Installation:
Let's start with a fresh Symfony installation optimized for API development:

# Run this to build a microservice, console application or API
symfony new symfony-app --version="7.3.x"
# move into your new project directory and start the local web server
cd symfony-app
symfony server:start

# Install dependencies
composer require symfony/maker-bundle --dev

Enter fullscreen mode Exit fullscreen mode

First page displayed after successfully installing and starting a Symfony 7.3 project.

Step 2 : Creating the API Endpoint to Display Products Data
Now that Symfony is installed and running, let's create a simple API endpoint that returns product data in JSON format.
This will serve as our backend data source for the frontend, and for now, we’ll keep it hard-coded so we can focus on the integration flow.

Create a new controller file at src/Controller/Api/ProductApiController.php

<?php

namespace App\Controller\Api;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

class ProductApiController extends AbstractController
{
    #[Route('/api/products', name: 'api_products', methods: ['GET'])]
    public function index(): JsonResponse
    {
        return $this->json([
            [
                'title' => 'Sample Product',
                'description' => 'This is a sample product description.',
            ],
            [
                'title' => 'Another Product',
                'description' => 'Another example description.',
            ],
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

📌 What’s happening here:
The #[Route] attribute maps /api/products to the index() method.
The $this->json() helper quickly sends back a JSON response.
We’ve returned an array of products, each with a title and description.

API endpoint /api/products returning hard-coded sample products for testing purposes.

‼️Note: In this article, the API returns hard-coded sample data so we can focus on structure and frontend integration.
See the follow-up article : Building a Dynamic API in Symfony with Doctrine and MySQL, where we connect to a database, store records, and retrieve them dynamically through the same endpoint.

“Great things are not done by impulse, but by a series of small things brought together.” - Vincent Van Gogh

Step 3 : Creating the React Application
With the API returning JSON data, let’s set up a simple React frontend to consume it.

Prereqs :

# choose a folder where you want the frontend
npm create vite@latest my-frontend -- --template react
cd my-frontend
npm install
Enter fullscreen mode Exit fullscreen mode

To run

npm run dev
Enter fullscreen mode Exit fullscreen mode

Vite will show a local development URL, usually http://localhost:5173. Keep this running while we develop.

React frontend setup via Vite, ready to consume Symfony API data.

Add a simple Products component : 
Create src/components/ProductList.jsx:
import { useEffect, useState } from "react";

export default function ProductList({ onSelect }) {
  const [products, setProducts] = useState([]);
  const [loading, setLoading]   = useState(true);
  const [error, setError]       = useState(null);

  useEffect(() => {
    fetch("/api/products")
      .then(async (res) => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
      })
      .then((data) => setProducts(data))
      .catch((err) => setError(err.message))
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error)   return <p style={{ color: "crimson" }}>Error: {error}</p>;
  if (!products.length) return <p>No products found.</p>;

  return (
    <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
      {products.map((p, i) => (
        <li
          key={i}
          onClick={() => onSelect?.(p)}
          style={{
            border: "1px solid #eee",
            borderRadius: 12,
            padding: "0.75rem 1rem",
            marginBottom: "0.75rem",
            cursor: "pointer",
          }}
        >
          <div style={{ fontWeight: 600 }}>{p.title}</div>
          <div style={{ fontSize: 14, opacity: 0.8, marginTop: 4 }}>
            {p.description}
          </div>
        </li>
      ))}
    </ul>
  );
}

Enter fullscreen mode Exit fullscreen mode

Use the component in src/App.jsx:

import { useState } from "react";
import "./App.css";
import ProductList from "./components/ProductList";

export default function App() {
  const [selected, setSelected] = useState(null);

  return (
    <main style={{ maxWidth: 900, margin: "2rem auto", fontFamily: "system-ui" }}>
      <h1 style={{ marginBottom: "1rem" }}>Products</h1>
      <div>
        <section>
          <ProductList onSelect={(p) => setSelected(p)} />
        </section>
      </div>
    </main>
  );
}

Enter fullscreen mode Exit fullscreen mode

Avoiding CORS issues with a dev proxy:
If your Symfony API runs at http://localhost:8000, proxy /api to it.
Create/edit vite.config.js:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      "/api": {
        target: "http://localhost:8000",
        changeOrigin: true,
      },
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

Now you can open http://localhost:5173 and see products loaded from http://localhost:8000/api/products.

Displaying product titles and descriptions fetched from Symfony API.

‼️Note : Run Symfony API (in another terminal) and Confirm http://localhost:8000/api/products returns your JSON.

Stats

Key Takeaways

  • Symfony offers a simple and clean approach for building frontend-ready APIs.
  • You can start with hard-coded JSON for quick integration tests before moving to dynamic data.
  • React (or any frontend) can consume Symfony API responses with minimal configuration.
  • Dev proxies are useful during development to avoid CORS issues.
  • Keeping the backend and frontend loosely coupled makes future scaling easier.

Interesting Facts

  • Symfony's API Platform supports GraphQL out of the box too.
  • You can auto-document your API using Swagger UI with no extra setup.
  • Over 10K companies worldwide use Symfony for APIs (source: symfony.com).

FAQs

Q1: Do I need Vue or React to test these APIs?
A: Nope. You can use Postman or a browser. Frontend is optional.

Q2: Is API Platform mandatory?
A: No, but it speeds things up a lot. You can write manual controllers if needed.

Q3: Can I add custom logic to endpoints?
A: Yes, you can override methods or use controllers for full control.

Conclusion

APIs are now central to building modern applications. By combining Symfony’s backend strengths with a frontend framework like React, you can create fast, maintainable, and scalable solutions. Starting with a hard-coded API response keeps the focus on integration and flow, helping you validate the connection between backend and frontend quickly.

This approach provides a strong foundation for the next step: moving from placeholder data to real, dynamic records.Continue with the follow-up article: Building ax Dynamic API in Symfony with Doctrine and MySQL.

About the Author: Balasaranya Varadhalingam, Software Engineer at AddWebSolution, specializes in PHP, Symfony, and API development.

Top comments (0)