<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Balasaranya Varadhalingam</title>
    <description>The latest articles on DEV Community by Balasaranya Varadhalingam (@balavaradhalingam).</description>
    <link>https://dev.to/balavaradhalingam</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3399401%2F919bdab4-a048-4872-a08b-223faf7d3a80.jpg</url>
      <title>DEV Community: Balasaranya Varadhalingam</title>
      <link>https://dev.to/balavaradhalingam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/balavaradhalingam"/>
    <language>en</language>
    <item>
      <title>Introducing the GTranslate Bundle</title>
      <dc:creator>Balasaranya Varadhalingam</dc:creator>
      <pubDate>Tue, 20 Jan 2026 07:59:11 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/introducing-the-gtranslate-bundle-5dg6</link>
      <guid>https://dev.to/addwebsolutionpvtltd/introducing-the-gtranslate-bundle-5dg6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Language is the road map of a culture. It tells you where its people come from and where they are going.” - Rita Mae Brown&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;What is the GTranslate Bundle?&lt;/li&gt;
&lt;li&gt;Why Does It Matter?&lt;/li&gt;
&lt;li&gt;How It Works

&lt;ul&gt;
&lt;li&gt;Default Behavior - Instant Setup&lt;/li&gt;
&lt;li&gt;Customizing the Widget&lt;/li&gt;
&lt;li&gt;Manual Placement Example&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stats&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;li&gt;Interesting Facts&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In today’s global web, multilingual accessibility isn’t a luxury, it’s a necessity. However, using translations in contemporary frameworks might take a lot of effort, particularly if you only want to translate your content fast and don't want to deal with complicated APIs or translation files.&lt;/p&gt;

&lt;p&gt;That’s exactly what the AddWeb &lt;a href="https://packagist.org/packages/addweb/gtranslate-bundle" rel="noopener noreferrer"&gt;GTranslate Bundle&lt;/a&gt; brings to Symfony developers. A lightweight, plug-and-play integration of the &lt;a href="http://GTranslate.io" rel="noopener noreferrer"&gt;GTranslate.io&lt;/a&gt; widget that lets your website speak multiple languages in minutes.&lt;/p&gt;

&lt;p&gt;Whether you’re building a content-heavy platform, SaaS dashboard, or portfolio site, the GTranslate Bundle eliminates manual i18n setup pain and provides effortless automatic translation right from Twig templates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the GTranslate Bundle?
&lt;/h2&gt;

&lt;p&gt;The GTranslate Bundle (addweb/gtranslate-bundle) is a Symfony 6.4+/7.x-compatible module that: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrates &lt;a href="http://Gtranslate.io" rel="noopener noreferrer"&gt;Gtranslate.io&lt;/a&gt;, a trusted AI-powered website translation widget.&lt;/li&gt;
&lt;li&gt;Provides a Twig function {{ gtranslate_widget() }} to render customizable translation widgets.&lt;/li&gt;
&lt;li&gt;Offers simple YAML configuration to define supported languages, default language, and UI preferences.&lt;/li&gt;
&lt;li&gt;Automatically injects optimized JavaScript for instant translation switching without backend complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes multilingual support as easy as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require addweb/gtranslate-bundle

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then simply add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ gtranslate_widget() }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Does It Matter?
&lt;/h2&gt;

&lt;p&gt;Businesses and developers often underestimate how language accessibility impacts engagement, SEO, and trust. According to CSA Research:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;65% of users prefer content in their own language, even if it’s of lower quality.&lt;/li&gt;
&lt;li&gt;40% won’t buy from websites in other languages.&lt;/li&gt;
&lt;li&gt;Multilingual websites experience up to 50% longer session durations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;a href="https://packagist.org/packages/addweb/gtranslate-bundle" rel="noopener noreferrer"&gt;this&lt;/a&gt; bundle, Symfony developers can deliver that global experience without managing translation files or third-party SDK integrations manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Default Behavior - Instant Setup&lt;/p&gt;

&lt;p&gt;Once you install the bundle using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require addweb/gtranslate-bundle

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Symfony Flex automatically:&lt;br&gt;
Registers the bundle in your project.&lt;br&gt;
Creates a default config file at &lt;strong&gt;&lt;code&gt;config/packages/gtranslate.yaml&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Makes the Twig function &lt;strong&gt;{{ gtranslate_widget() }}&lt;/strong&gt; available globally.&lt;/p&gt;

&lt;p&gt;In your Twig layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ gtranslate_widget() }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The bundle injects the proper HTML and JS, rendering the GTranslate floating widget automatically.&lt;/li&gt;
&lt;li&gt;Configuration changes apply instantly. No rebuild or cache-clear needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Default Widget Output :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftkomm5wbuehqo45hzkud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftkomm5wbuehqo45hzkud.png" alt=" " width="659" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh98i54dai2rawtpwhkgd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh98i54dai2rawtpwhkgd.png" alt=" " width="553" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customizing the Widget&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can modify the default behavior easily in &lt;strong&gt;config/packages/gtranslate.yaml&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gtranslate:
  script_src: 'https://cdn.gtranslate.net/widgets/latest/dwf.js'
  auto_wrapper: true
  settings:
    default_language: 'en'
    languages: ['en','fr','it','es']
    wrapper_selector: '.gtranslate_wrapper'
    switcher_horizontal_position: 'right'
    switcher_vertical_position: 'top'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These changes allow you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add or remove supported languages&lt;/li&gt;
&lt;li&gt;Change widget colors and positions&lt;/li&gt;
&lt;li&gt;Choose between floating or embedded widget&lt;/li&gt;
&lt;li&gt;Disable the wrapper and place the switcher manually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OUTPUT:&lt;br&gt;
In English&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figv7t49wd6ezkvjizktc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figv7t49wd6ezkvjizktc.png" alt=" " width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Spanish&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4994g4em36a0ptpgck8i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4994g4em36a0ptpgck8i.png" alt=" " width="800" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Expanded dropdown&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwz3asf7uxg8pdmmcscpv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwz3asf7uxg8pdmmcscpv.png" alt=" " width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual Placement Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to render it inside a specific div instead of floating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="language-switcher"&amp;gt;
    {{ gtranslate_widget({ auto_wrapper: false }) }}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add your custom CSS or styling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Websites with multilingual support experience up to a 50% increase in average session duration. &lt;a href="https://www.weglot.com/blog/multilingual-website-platforms" rel="noopener noreferrer"&gt;https://www.weglot.com/blog/multilingual-website-platforms&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;14% higher conversion rates are observed on multilingual landing pages.&lt;a href="https://landingi.com/landing-page/high-converting-examples/" rel="noopener noreferrer"&gt;https://landingi.com/landing-page/high-converting-examples/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Over 7 in 10 consumers are more likely to engage with brands speaking their language. &lt;a href="https://csa-research.com/Blogs-Events/CSA-in-the-Media/Press-Releases/Consumers-Prefer-their-Own-Language" rel="noopener noreferrer"&gt;https://csa-research.com/Blogs-Events/CSA-in-the-Media/Press-Releases/Consumers-Prefer-their-Own-Language&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seamless integration with Symfony Flex and Twig.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Supports 100+ languages powered by &lt;a href="http://GTranslate.io" rel="noopener noreferrer"&gt;GTranslate.io&lt;/a&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Zero dependency on local translation files or databases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Simple YAML configuration. No code changes needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extensible and open-source, ready for contribution.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Interesting Facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The internet hosts content in more than 7,000 languages.&lt;a href="https://www.ethnologue.com/insights/how-many-languages" rel="noopener noreferrer"&gt;https://www.ethnologue.com/insights/how-many-languages&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;65% of users prefer browsing websites in their native language, even if the translation isn’t perfect.&lt;/li&gt;
&lt;li&gt;Websites with multilingual support rank 23% higher on Google’s international search results.&lt;a href="https://developers.google.com/search/docs/specialty/international" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/specialty/international&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI translation accuracy has improved over 92% since 2018, thanks to neural models.&lt;a href="https://research.google/blog/zero-shot-translation-with-googles-multilingual-neural-machine-translation-system/" rel="noopener noreferrer"&gt;https://research.google/blog/zero-shot-translation-with-googles-multilingual-neural-machine-translation-system/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GTranslate.io currently supports over 100 languages, making it one of the widest coverage translation tools. &lt;a href="https://gtranslate.io/" rel="noopener noreferrer"&gt;https://gtranslate.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Websites using GTranslate widgets load 1.8× faster compared to full i18n setups with translation catalogs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: How is this different from the Symfony Translation Component?&lt;/strong&gt;&lt;br&gt;
A: Symfony’s translation component requires manual .xliff or .yaml message catalogs.&lt;br&gt;
Addweb GTranslate Bundle provides automatic, real-time translation using GTranslate.io.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: Does it need a GTranslate API key?&lt;/strong&gt;&lt;br&gt;
A: No. It uses the public widget (free or premium account). You just configure supported languages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Is it compatible with Twig themes?&lt;/strong&gt;&lt;br&gt;
A: Yes. Works with any Twig-based theme or layout file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: Can I customize the widget position or color?&lt;/strong&gt;&lt;br&gt;
A: Absolutely. Use YAML settings for switcher_horizontal_position, colors, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;“Translation is that which transforms everything so that nothing changes.” - Günter Grass&lt;/p&gt;

&lt;p&gt;The AddWeb GTranslate Bundle is not just another Symfony plugin, it’s a bridge between your brand and the world. It gives every developer the power to build websites that speak to everyone, everywhere, with zero configuration overhead.&lt;/p&gt;

&lt;p&gt;You can explore it on Packagist and contribute via GitHub: addweb/gtranslate-bundle&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About the Author&lt;/strong&gt;: Balasaranya Varadhalingam, Software Engineer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt;, specializes in PHP, Symfony, and API development. &lt;/p&gt;

</description>
      <category>symfony</category>
      <category>gtranslate</category>
      <category>localization</category>
      <category>internationalization</category>
    </item>
    <item>
      <title>Building a Dynamic API in Symfony with Doctrine and MySQL</title>
      <dc:creator>Balasaranya Varadhalingam</dc:creator>
      <pubDate>Wed, 24 Sep 2025 07:39:01 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/building-a-dynamic-api-in-symfony-with-doctrine-and-mysql-5gg6</link>
      <guid>https://dev.to/addwebsolutionpvtltd/building-a-dynamic-api-in-symfony-with-doctrine-and-mysql-5gg6</guid>
      <description>&lt;p&gt;&lt;em&gt;Take your Symfony API to the next level by connecting it to a real database. In this blog, you’ll learn how to use Doctrine ORM and MySQL to store, retrieve, and serve dynamic data making your backend ready for React, Vue, Angular, or any modern frontend.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Building a Dynamic API in Symfony with Doctrine and MySQL&lt;/p&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction

&lt;ul&gt;
&lt;li&gt;Step 1: Setting Up Database Configuration in Symfony&lt;/li&gt;
&lt;li&gt;Step 2: Installing Doctrine ORM&lt;/li&gt;
&lt;li&gt;Step 3: Creating the Entity&lt;/li&gt;
&lt;li&gt;Step 4: Running Migrations

&lt;ul&gt;
&lt;li&gt;Generate the migration file&lt;/li&gt;
&lt;li&gt;Apply the migration to the database&lt;/li&gt;
&lt;li&gt;Verify the table was created&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Step 5: Adding and Retrieving Products from the Database

&lt;ul&gt;
&lt;li&gt;To Create the Fixtures Class&lt;/li&gt;
&lt;li&gt;Load the Fixtures into the Database&lt;/li&gt;
&lt;li&gt;Update the API Controller to Fetch from the Database&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Step 6: Viewing the Data in React&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stats&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;li&gt;Interesting Facts&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;About the Author&lt;/li&gt;
&lt;li&gt;SEO Settings&lt;/li&gt;
&lt;li&gt;Hashtags&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;APIs become far more valuable when they serve real, dynamic data instead of fixed, hard-coded responses. In this guide, we’ll extend our Symfony API to connect with a MySQL database, enabling it to store, retrieve, and return data in real time. Using Doctrine ORM, we’ll map database tables to PHP entities, run migrations to set up the schema, and integrate the data into our API endpoints. By the end, you’ll have a fully functional, database-powered API that’s ready to serve any frontend application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The man who moves a mountain begins by carrying away small stones.” - Confucius&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;Step 1: Setting Up Database Configuration in Symfony&lt;/strong&gt;&lt;/u&gt;&lt;br&gt;
Before we can store and retrieve data, Symfony needs to know how to connect to your database. This is configured in the .env file located in the root of your project.&lt;br&gt;
Open .env in your editor and look for the DATABASE_URL line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&amp;amp;charset=utf8mb4"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace it with your MySQL connection details. For Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL="mysql://root:root@127.0.0.1:3306/symfony-app?serverVersion=8.0.32&amp;amp;charset=utf8mb4"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation of each part:&lt;br&gt;
root:root : The database username and password.&lt;br&gt;
127.0.0.1:3306 : Host and port where MySQL is running.&lt;br&gt;
symfony : The name of your database.&lt;br&gt;
serverVersion=8.0.32 : The exact MySQL version you’re using.&lt;br&gt;
charset=utf8mb4 : Character encoding for full Unicode support (including emojis).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Store sensitive credentials in .env.local instead of .env so they are not committed to version control.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;Step 2: Installing Doctrine ORM&lt;/strong&gt;&lt;/u&gt;&lt;br&gt;
Doctrine is Symfony’s default ORM (Object Relational Mapper). It acts as a bridge between your PHP objects (entities) and the database tables. We’ll use it to create tables, store data, and fetch it in our API.&lt;/p&gt;

&lt;p&gt;To install Doctrine ORM and related tools, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require symfony/orm-pack
composer require --dev doctrine/doctrine-fixtures-bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What these packages do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;symfony/orm-pack :&lt;/strong&gt; Installs Doctrine ORM, database migrations, and configuration files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;doctrine/doctrine-fixtures-bundle :&lt;/strong&gt; Allows you to load sample data for testing. (optional but useful during development)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once installed, Symfony will create a default config/packages/doctrine.yaml file where Doctrine’s settings are stored. It will also automatically use the DATABASE_URL you set up in Step 1.&lt;/p&gt;

&lt;p&gt;To verify that Doctrine is installed, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php bin/console doctrine:database:create

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is configured correctly, you’ll see a message confirming that the database has been created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created database `symfony-app` for connection named default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Creating the Entity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/addwebsolutionpvtltd/building-powerful-apis-in-symfony-a-complete-guide-for-modern-frontend-integration-59am"&gt;previous blog&lt;/a&gt;, our /api/products endpoint returned hardcoded JSON data.&lt;br&gt;
Now, we’ll make it dynamic by connecting it to a database table that stores real product entries.&lt;br&gt;
To do this, we need a Product entity that Doctrine ORM will map to a database table.&lt;br&gt;
An Entity in Symfony represents a database table, with each property mapping to a column. We’ll create a Product entity containing title, description, and createdAt fields.&lt;/p&gt;

&lt;p&gt;Run the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php bin/console make:entity

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompted, enter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lql4s186ilcd39rg0vm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lql4s186ilcd39rg0vm.png" alt=" " width="625" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Defining the Product entity with title, description, and createdAt fields.&lt;/p&gt;

&lt;p&gt;Symfony will generate src/Entity/Product.php.&lt;br&gt;
It will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Entity;

use App\Repository\ProductRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: ProductRepository::class)]
class Product
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    #[ORM\Column(type: Types::TEXT)]
    private ?string $description = null;

    #[ORM\Column]
    private ?\DateTimeImmutable $createdAt = null;

    public function getId(): ?int
    {
        return $this-&amp;gt;id;
    }

    public function getTitle(): ?string
    {
        return $this-&amp;gt;title;
    }

    public function setTitle(string $title): static
    {
        $this-&amp;gt;title = $title;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this-&amp;gt;description;
    }

    public function setDescription(string $description): static
    {
        $this-&amp;gt;description = $description;

        return $this;
    }

    public function getCreatedAt(): ?\DateTimeImmutable
    {
        return $this-&amp;gt;createdAt;
    }

    public function setCreatedAt(\DateTimeImmutable $createdAt): static
    {
        $this-&amp;gt;createdAt = $createdAt;

        return $this;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;&lt;strong&gt;Step 4: Running Migrations&lt;/strong&gt;&lt;/u&gt;&lt;br&gt;
Now that we’ve created the Product entity, we’ll make it an actual database table so our API can serve real records. Symfony uses Doctrine migrations to translate entity definitions into database schema changes. Running a migration will create the product table in MySQL with the fields we defined in step 3.&lt;br&gt;
&lt;strong&gt;&lt;u&gt;Generate the migration file&lt;/u&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php bin/console make:migration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87pk9ol2ojhnesdda80i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87pk9ol2ojhnesdda80i.png" alt=" " width="625" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Migration file generated successfully with make:migration.&lt;/p&gt;

&lt;p&gt;This creates a new migration class in the migrations/ directory.&lt;br&gt;
If you open it, you’ll see SQL statements that Doctrine will execute for example, creating the product table with id, title, description, and created_at columns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;Apply the migration to the database&lt;/u&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php bin/console doctrine:migrations:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll be asked to confirm. Type yes and press Enter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmlwfxq1bwbavvl05rj2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmlwfxq1bwbavvl05rj2w.png" alt=" " width="628" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Migration applied successfully with doctrine:migrations:migrate.&lt;br&gt;
Verify the table was created&lt;br&gt;
Log into MySQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql -u root -p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USE symfony-app;
SHOW TABLES;
DESCRIBE product;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkptini0ghlq8v1thk32w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkptini0ghlq8v1thk32w.png" alt=" " width="628" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see your product table with the columns we defined in the Product entity.&lt;/p&gt;

&lt;p&gt;At this point, our Symfony app is ready to store and fetch product data from the database. In the next step, we’ll add some records so our API can return them instead of the placeholder JSON.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Well done is better than well said.” - Benjamin Franklin&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;Step 5: Adding and Retrieving Products from the Database&lt;/strong&gt;&lt;/u&gt;&lt;br&gt;
We’ll use Doctrine Fixtures to insert sample records. Fixtures are a convenient way to load data into your database for development and testing.&lt;/p&gt;

&lt;p&gt;Install orm-fixtures package using composer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require orm-fixtures --dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To Create the Fixtures Class&lt;br&gt;
Run :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php bin/console make:fixtures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The class name of the fixtures to create (e.g. AppFixtures):
 &amp;gt; ProductFixtures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Symfony will create &lt;strong&gt;src/DataFixtures/ProductFixtures.php.&lt;/strong&gt;&lt;br&gt;
Update it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\DataFixtures;

use App\Entity\Product;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;

class ProductFixtures extends Fixture
{
   public function load(ObjectManager $manager): void
   {
       $products = [
           ['title' =&amp;gt; 'Sample Product', 'description' =&amp;gt; 'This is a sample product description.'],
           ['title' =&amp;gt; 'Another Product', 'description' =&amp;gt; 'Another example description.'],
           ['title' =&amp;gt; 'Third Product', 'description' =&amp;gt; 'Yet another product for testing.'],
       ];

       foreach ($products as $data) {
           $product = new Product();
           $product-&amp;gt;setTitle($data['title']);
           $product-&amp;gt;setDescription($data['description']);
           // createdAt will be set automatically by the constructor
           $manager-&amp;gt;persist($product);
       }

       $manager-&amp;gt;flush();
   }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want every record to have a creation timestamp without having to set it in fixtures or forms.&lt;br&gt;
So, update src/Entity/Product.php constructor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function __construct()
{
    $this-&amp;gt;createdAt = new \DateTimeImmutable();
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;Load the Fixtures into the Database&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php bin/console doctrine:fixtures:load

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll be asked to confirm. Type &lt;strong&gt;yes&lt;/strong&gt;.&lt;br&gt;
Doctrine will insert the sample records into the product table.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Update the API Controller to Fetch from the Database&lt;/u&gt;&lt;br&gt;
Replace the hard-coded JSON in src/Controller/Api/ProductApiController.php with a database query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Controller\Api;

use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
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(EntityManagerInterface $em): JsonResponse
    {
        $products = $em-&amp;gt;getRepository(Product::class)-&amp;gt;findAll();

        $data = [];
        foreach ($products as $product) {
            $data[] = [
                'title' =&amp;gt; $product-&amp;gt;getTitle(),
                'description' =&amp;gt; $product-&amp;gt;getDescription(),
            ];
        }

        return $this-&amp;gt;json($data);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when you visit &lt;a href="http://localhost:8000/api/products" rel="noopener noreferrer"&gt;http://localhost:8000/api/products&lt;/a&gt; you’ll see data coming from the database instead of hard-coded arrays.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46yo0dmn7knmjm6mx6p5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46yo0dmn7knmjm6mx6p5.png" alt=" " width="624" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;symfony product API result&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;Step 6: Viewing the Data in React&lt;/u&gt;&lt;/strong&gt;&lt;br&gt;
Now, instead of returning a fixed array, Symfony fetches records directly from the database using Doctrine.&lt;/p&gt;

&lt;p&gt;Open your React app at: &lt;a href="http://localhost:5173" rel="noopener noreferrer"&gt;http://localhost:5173&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj221g1tyd6w32n6ne0uz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj221g1tyd6w32n6ne0uz.png" alt=" " width="627" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;React frontend now showing products retrieved from the Symfony API with data stored in the database.&lt;/p&gt;

&lt;p&gt;You’ll see the same product list UI, but this time the content is coming from the database through Symfony’s API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“RESTful APIs built with Symfony give you the freedom to power anything from a simple React app to an enterprise platform, all with the same clean backend.” - SensioLabs Engineering Team&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Stats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Symfony’s &lt;a href="https://symfony.com/" rel="noopener noreferrer"&gt;official usage report&lt;/a&gt; shows that over 600,000 developers worldwide rely on Symfony and Doctrine ORM for building robust applications.&lt;/li&gt;
&lt;li&gt;According to &lt;a href="https://github.com/doctrine/orm" rel="noopener noreferrer"&gt;Doctrine’s GitHub repository&lt;/a&gt;, the ORM has been downloaded more than 1 billion times, making it one of the most widely used PHP database abstraction layers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Symfony and Doctrine provide a clean way to connect APIs to a database.&lt;/li&gt;
&lt;li&gt;Entities map PHP classes to database tables, making your code expressive and maintainable.&lt;/li&gt;
&lt;li&gt;Doctrine Migrations handle schema updates safely without manual SQL.&lt;/li&gt;
&lt;li&gt;Fixtures make it simple to preload sample data for development and testing.&lt;/li&gt;
&lt;li&gt;By swapping out hard-coded JSON with real database queries, your API instantly becomes dynamic and production-ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Interesting Facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Doctrine ORM is used in thousands of Symfony projects worldwide, making it one of the most battle-tested ORMs in the PHP ecosystem.&lt;/li&gt;
&lt;li&gt;Symfony’s database layer can connect not only to MySQL but also PostgreSQL, SQLite, and even Oracle.&lt;/li&gt;
&lt;li&gt;With Doctrine’s query builder, you can write expressive queries in PHP without switching between SQL and PHP code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: Can I use PostgreSQL instead of MySQL with Symfony?&lt;/strong&gt;&lt;br&gt;
A: Yes. Doctrine supports multiple database engines. You only need to update your DATABASE_URL in .env&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: Do I need fixtures in production?&lt;/strong&gt;&lt;br&gt;
A: No. Fixtures are primarily for development and testing. In production, you typically insert data through your application or migrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: What happens if I change my entity later?&lt;/strong&gt;&lt;br&gt;
A: You can run php bin/console make:migration and then php bin/console doctrine:migrations:migrate to safely update your database schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Moving from hard-coded data to a database-driven API is a key milestone in backend development. With Symfony, Doctrine, and MySQL, the process is straightforward and sets up a strong foundation for scalable applications. Once your API is connected to real data, you unlock endless possibilities whether that’s powering a React frontend, a mobile app, or other client systems. This step transforms your API from a demo into something ready for real-world use.&lt;/p&gt;

&lt;p&gt;About the Author: &lt;em&gt;Balasaranya Varadhalingam, Software Engineer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt;, specializes in PHP, Symfony, and API development.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>symfony</category>
      <category>doctrine</category>
      <category>restapi</category>
      <category>mysql</category>
    </item>
    <item>
      <title>Building Powerful APIs in Symfony: A Complete Guide for Modern Frontend Integration</title>
      <dc:creator>Balasaranya Varadhalingam</dc:creator>
      <pubDate>Mon, 22 Sep 2025 04:59:04 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/building-powerful-apis-in-symfony-a-complete-guide-for-modern-frontend-integration-59am</link>
      <guid>https://dev.to/addwebsolutionpvtltd/building-powerful-apis-in-symfony-a-complete-guide-for-modern-frontend-integration-59am</guid>
      <description>&lt;p&gt;&lt;em&gt;Building Powerful APIs in Symfony: A Complete Guide for Modern Frontend Integration&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Understanding Symfony API Architecture&lt;/li&gt;
&lt;li&gt;Core Components for API Development&lt;/li&gt;
&lt;li&gt;Setting Up Your Symfony API Project

&lt;ul&gt;
&lt;li&gt;Step 1: Installation:&lt;/li&gt;
&lt;li&gt;Step 2 :  Creating the API Endpoint to Display Products Data&lt;/li&gt;
&lt;li&gt;Step 3 : Creating the React Application&lt;/li&gt;
&lt;li&gt;Now, To Create the React app (Vite)&lt;/li&gt;
&lt;li&gt;Add a simple Products component&lt;/li&gt;
&lt;li&gt;Use the component in src/App.jsx&lt;/li&gt;
&lt;li&gt;Avoiding CORS issues with a dev proxy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stats&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;li&gt;Interesting Facts&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The journey of a thousand miles begins with a single step.” - Lao Tzu&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Understanding Symfony API Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"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&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Core Components for API Development&lt;/strong&gt;&lt;br&gt;
Controllers : Handle HTTP requests and return JSON responses.&lt;br&gt;
Routes &amp;amp; Attributes : Define clear API paths using route annotations.&lt;br&gt;
Serializer : Convert PHP arrays or objects into JSON for frontend consumption.&lt;br&gt;
HTTP Foundation : Manage requests and responses effectively.&lt;br&gt;
Security Layer : Optional for protected APIs, handling authentication and authorization.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting Up Your Symfony API Project
&lt;/h2&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;Step 1: Installation:&lt;/strong&gt;&lt;/u&gt;&lt;br&gt;
Let's start with a fresh Symfony installation optimized for API development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hhzmyyal820rwkg2y7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hhzmyyal820rwkg2y7w.png" alt=" " width="628" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First page displayed after successfully installing and starting a Symfony 7.3 project.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;Step 2 :  Creating the API Endpoint to Display Products Data&lt;/strong&gt;&lt;/u&gt;&lt;br&gt;
Now that Symfony is installed and running, let's create a simple API endpoint that returns product data in JSON format.&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;Create a new controller file at src/Controller/Api/ProductApiController.php&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?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-&amp;gt;json([
            [
                'title' =&amp;gt; 'Sample Product',
                'description' =&amp;gt; 'This is a sample product description.',
            ],
            [
                'title' =&amp;gt; 'Another Product',
                'description' =&amp;gt; 'Another example description.',
            ],
        ]);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fve2yplvr6o44ae580ytt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fve2yplvr6o44ae580ytt.png" alt=" " width="625" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;API endpoint /api/products returning hard-coded sample products for testing purposes.&lt;/p&gt;

&lt;p&gt;‼️Note: In this article, the API returns hard-coded sample data so we can focus on structure and frontend integration.&lt;br&gt;
See the follow-up article : &lt;a href="https://dev.to/addwebsolutionpvtltd/building-a-dynamic-api-in-symfony-with-doctrine-and-mysql-5gg6"&gt;Building a Dynamic API in Symfony with Doctrine and MySQL&lt;/a&gt;, where we connect to a database, store records, and retrieve them dynamically through the same endpoint.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Great things are not done by impulse, but by a series of small things brought together.” - Vincent Van Gogh&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;Step 3 : Creating the React Application&lt;/strong&gt;&lt;/u&gt;&lt;br&gt;
With the API returning JSON data, let’s set up a simple React frontend to consume it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prereqs :&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 18+ (check: node -v)&lt;/li&gt;
&lt;li&gt;Your Symfony API running locally (e.g. &lt;a href="http://localhost:8000/api/products" rel="noopener noreferrer"&gt;http://localhost:8000/api/products&lt;/a&gt;)
Now, To Create the React app (Vite):
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# choose a folder where you want the frontend
npm create vite@latest my-frontend -- --template react
cd my-frontend
npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vite will show a local development URL, usually &lt;a href="http://localhost:5173" rel="noopener noreferrer"&gt;http://localhost:5173&lt;/a&gt;. Keep this running while we develop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5bnrefbzt8szpqirk3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5bnrefbzt8szpqirk3w.png" alt=" " width="365" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;React frontend setup via Vite, ready to consume Symfony API data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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(() =&amp;gt; {
    fetch("/api/products")
      .then(async (res) =&amp;gt; {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
      })
      .then((data) =&amp;gt; setProducts(data))
      .catch((err) =&amp;gt; setError(err.message))
      .finally(() =&amp;gt; setLoading(false));
  }, []);

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

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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the component in &lt;strong&gt;src/App.jsx:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState } from "react";
import "./App.css";
import ProductList from "./components/ProductList";

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

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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Avoiding CORS issues with a dev proxy:&lt;/strong&gt;&lt;br&gt;
If your Symfony API runs at &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt;, proxy /api to it.&lt;br&gt;
Create/edit vite.config.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      "/api": {
        target: "http://localhost:8000",
        changeOrigin: true,
      },
    },
  },
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can open &lt;a href="http://localhost:5173" rel="noopener noreferrer"&gt;http://localhost:5173&lt;/a&gt; and see products loaded from &lt;a href="http://localhost:8000/api/products" rel="noopener noreferrer"&gt;http://localhost:8000/api/products&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnerp5rcwcrxzhbwnwaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnerp5rcwcrxzhbwnwaz.png" alt=" " width="630" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Displaying product titles and descriptions fetched from Symfony API.&lt;/p&gt;

&lt;p&gt;‼️Note : Run Symfony API (in another terminal) and Confirm &lt;a href="http://localhost:8000/api/products" rel="noopener noreferrer"&gt;http://localhost:8000/api/products&lt;/a&gt;  returns your JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;According to the 2024 &lt;a href="https://survey.stackoverflow.co/2024/" rel="noopener noreferrer"&gt;Stack Overflow Developer Survey&lt;/a&gt;, over 87% of developers use APIs regularly, with REST APIs being the most popular choice at 71% adoption rate.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mulesoft.com/lp/reports/connectivity-benchmark" rel="noopener noreferrer"&gt;A MuleSoft 2023 Connectivity Benchmark&lt;/a&gt; Report found that integrating backend APIs with frontend frameworks can reduce development time by up to 40% for multi-platform applications.&lt;/li&gt;
&lt;li&gt;According to &lt;a href="https://www.postman.com/state-of-api/" rel="noopener noreferrer"&gt;Postman’s 2024 State of the API Report&lt;/a&gt;, over 80% of software teams now design their applications with an API-first approach.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  Interesting Facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Symfony's API Platform supports GraphQL out of the box too.&lt;/li&gt;
&lt;li&gt;You can auto-document your API using Swagger UI with no extra setup.&lt;/li&gt;
&lt;li&gt;Over 10K companies worldwide use Symfony for APIs (source: &lt;a href="http://symfony.com" rel="noopener noreferrer"&gt;symfony.com&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1: Do I need Vue or React to test these APIs?&lt;/strong&gt;&lt;br&gt;
A: Nope. You can use Postman or a browser. Frontend is optional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: Is API Platform mandatory?&lt;/strong&gt;&lt;br&gt;
A: No, but it speeds things up a lot. You can write manual controllers if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: Can I add custom logic to endpoints?&lt;/strong&gt;&lt;br&gt;
A: Yes, you can override methods or use controllers for full control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;About the Author: &lt;em&gt;Balasaranya Varadhalingam, Software Engineer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt;, specializes in PHP, Symfony, and API development.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>apiplatform</category>
      <category>restapi</category>
      <category>vue</category>
    </item>
    <item>
      <title>Secure Drupal: Best Practices for Enterprise Sites</title>
      <dc:creator>Balasaranya Varadhalingam</dc:creator>
      <pubDate>Mon, 04 Aug 2025 05:59:58 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/secure-drupal-best-practices-for-enterprise-sites-37mi</link>
      <guid>https://dev.to/addwebsolutionpvtltd/secure-drupal-best-practices-for-enterprise-sites-37mi</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Small habits, when repeated daily, become big results.” &lt;br&gt;
        - James Clear, author of Atomic Habits&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Phase 1: The Development Period – Building a Secure Foundation&lt;/li&gt;
&lt;li&gt;Phase 2: The Review Period – Automating Security with a CI/CD Pipeline&lt;/li&gt;
&lt;li&gt;Phase 3: The Deployment Stage – Hardening The Live Environment&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;li&gt;Stats&lt;/li&gt;
&lt;li&gt;Interesting Facts&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Strengthening Security from Development to Deployment.&lt;br&gt;
Drupal is one of the most powerful content management platforms used by enterprise organizations. It offers flexibility, scalability, and a strong foundation for building complex digital experiences. However, its open-source nature and modular structure also demand a proactive approach to security. With the increasing sophistication of cyber threats, a "set it and forget it" approach to security is no longer viable. A truly secure website isn’t the result of a last-minute checklist; it’s the outcome of a security-first mindset integrated into every stage of the development lifecycle. &lt;br&gt;
Enterprise sites, in particular, attract more attention from malicious actors because of the value of their data and visibility. That’s why it’s important to build security into the project from the very beginning right from development and continue applying best practices throughout the site’s lifecycle.&lt;/p&gt;

&lt;p&gt;In this post, I’ll walk through how we can secure our Drupal sites across all three main stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Development&lt;/li&gt;
&lt;li&gt;Review&lt;/li&gt;
&lt;li&gt;Deployment
We’ll also see how certain Drupal modules and tools can make this process easier and more effective.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Phase 1: The Development Period – Building a Secure Foundation
&lt;/h2&gt;

&lt;p&gt;Security starts the moment the first line of code is written. In fact, the choices we make during development shape how well the site can defend itself in the future. If we ignore security early on, we might end up patching issues later, which is harder and riskier. But when we build the site with safety in mind from the beginning, we make everything stronger and easier to manage in the long run.&lt;/p&gt;

&lt;p&gt;One of the most important things to do in the early stages is to install and configure the &lt;a href="https://www.drupal.org/project/seckit" rel="noopener noreferrer"&gt;Security Kit (SecKit) module&lt;/a&gt;. Think of SecKit as a digital shield that adds layers of protection around your site. It helps the browser understand what it should and shouldn’t accept so even if someone tries something malicious, the browser won’t allow it. Instead of writing code first and worrying about vulnerabilities later, we can use SecKit to guide our development process. With it turned on early, we’re writing code that already respects important safety rules. This approach saves us time, avoids bugs later, and makes the project cleaner overall.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh9iyqnlzxyksvublsvu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh9iyqnlzxyksvublsvu.png" alt=" " width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, Let’s explore how SecKit helps during development:&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Protection Against Cross-Site Scripting (XSS)&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;XSS is one of the most common attacks on websites. It happens when someone manages to sneak harmful scripts (like JavaScript) into forms, comments, or URLs. These scripts can steal user data, show fake messages, or redirect users to another site.&lt;/li&gt;
&lt;li&gt;SecKit helps reduce this risk by controlling what kinds of content are allowed. For example, it can stop the browser from running scripts unless they come from safe sources. It also sets security headers that tell the browser, “Don’t allow this page to do certain things.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftzz6od5yuofhym4yz2qf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftzz6od5yuofhym4yz2qf.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By applying these restrictions during development, we’re building our features in a way that naturally blocks harmful behavior without breaking normal site functionality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;u&gt;Cross-Site Request Forgery (CSRF) Protection&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSRF is another sneaky trick attackers use to fool logged-in users into doing something they didn’t mean to. Imagine you're logged into a Drupal admin panel in one tab, and you visit a shady website in another. That website might silently submit a request to change your password without you clicking anything.&lt;/li&gt;
&lt;li&gt;SecKit helps by telling the browser that all actions (like submitting forms or saving content) must come from users who actually interacted with the site. It blocks suspicious requests that try to act on your behalf without your knowledge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23z9zcbxye3utqk6jfo1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F23z9zcbxye3utqk6jfo1.png" alt=" " width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Defending Against Clickjacking&lt;/u&gt;&lt;br&gt;
Clickjacking is a trick where attackers hide your site inside an invisible frame (iframe) on their page. They then place fake buttons on top, so when someone thinks they’re clicking on “Play Video,” they’re actually clicking “Delete Account” on your site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6es73tl6rfb2hzm3b7ps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6es73tl6rfb2hzm3b7ps.png" alt=" " width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To stop this, SecKit sends a rule to the browser: “Don’t allow this page to be embedded anywhere.” That simple rule prevents outsiders from hijacking your site's interface.&lt;/li&gt;
&lt;li&gt;When we enable this setting during development, we don’t have to worry later about which pages are safe and which aren’t. It applies protection across the board.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configuring SecKit from day one forces developers to work within a secure framework, making it a foundational part of the site’s architecture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The future depends on what you do today.” - Mahatma Gandhi&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Phase 2: The Review Period – Automating Security with a CI/CD Pipeline
&lt;/h2&gt;

&lt;p&gt;After the development work begins, we reach a critical point in the review phase. This is where code is double-checked, tested, and filtered before it reaches the live site. But instead of doing all the checking manually, we can automate most of it using a smart CI/CD (Continuous Integration and Continuous Deployment) pipeline.&lt;br&gt;
Think of this phase as the site’s first security checkpoint. It catches problems while they are still easy to fix. The goal here is simple: stop bad code before it becomes part of the project.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;What is a CI/CD Pipeline?&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;A CI/CD pipeline is a series of automated steps that run whenever someone writes new code or makes changes. It’s like a robot assistant that checks your work every time you save it. These steps can do many things from checking code quality to scanning for security risks to running all the tests that simulate user activity.&lt;br&gt;
By putting security checks into this pipeline, we create a strong defense system that works in the background while we build features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Static Code Analysis with PHP_CodeSniffer (phpcs)&lt;/strong&gt;&lt;br&gt;
One of the first things we can add to our pipeline is PHP_CodeSniffer. This tool reads through the code and compares it with Drupal’s coding standards. It doesn’t just look for formatting it also highlights risky patterns that could later cause security issues.&lt;/p&gt;

&lt;p&gt;For example, if a developer forgets to sanitize user input, or directly prints something without filtering it, phpcs will flag it. It acts like a teacher reviewing your homework pointing out anything that doesn’t match Drupal’s best practices.&lt;br&gt;
This step helps every developer on the team write consistent and safer code, even if they are new to Drupal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behind the Scenes:&lt;/strong&gt;&lt;br&gt;
When a developer pushes code to Git, the CI system automatically runs &lt;strong&gt;phpcs&lt;/strong&gt;. If there are violations, the pipeline stops and gives feedback. The developer can then fix the issues and resubmit. This keeps problems out of the codebase before they grow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Scan for Vulnerabilities in Dependencies&lt;/strong&gt;&lt;br&gt;
Modern Drupal sites use many third-party libraries : modules, composer packages, and plugins. These tools save time, but they can also bring hidden risks. If one of them has a known vulnerability, attackers can use that weak point to access or damage the site.&lt;/p&gt;

&lt;p&gt;To catch these kinds of problems, we can use a &lt;strong&gt;Composer Audit&lt;/strong&gt;. This tool compares our project’s dependencies with a database of reported vulnerabilities. If any of the packages are known to be unsafe, the pipeline will show a warning right away.&lt;br&gt;
This is like checking the ingredients of a recipe to make sure none of them are expired or harmful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Automated Testing – Stop Bugs Before They Go Live&lt;/strong&gt;&lt;br&gt;
Even if the code is written cleanly and libraries are safe, mistakes can still happen. A small change in one area might accidentally break something else. That’s why we run automated tests in the CI/CD pipeline.&lt;br&gt;
These tests include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests&lt;/strong&gt; that check small pieces of code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kernel tests&lt;/strong&gt; that look at how parts of Drupal interact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser tests&lt;/strong&gt; that simulate real user behavior (like logging in, submitting forms, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these tests fail, the code doesn’t move forward. It stays in review until the issue is fixed.&lt;br&gt;
This step gives us confidence that every change we make keeps the site working as expected. It also reduces the chance of introducing bugs that might affect users or create new security problems.&lt;/p&gt;

&lt;p&gt;By automating this process, we avoid rushing through reviews or missing something important. We also save time, because developers don’t need to manually review every detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Phase 3: The Deployment Stage – Hardening The Live Environment
&lt;/h2&gt;

&lt;p&gt;Once the code has been built and reviewed, we reach the final phase: deployment. This is where the website leaves the comfort of development and enters the real world. It now faces real users, real traffic and real threats.&lt;/p&gt;

&lt;p&gt;At this stage, our job is to lock down everything around the site. Think of it like moving into a new house: even if the house is well-built, we still need to add locks, cameras, and other safety tools to protect it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Keep Non-Production Environments Hidden&lt;/strong&gt;&lt;br&gt;
Before we even talk about the live site, we need to talk about non-production environments like dev, staging, UAT, and testing. These are often copies of the real site, used for trying out features or fixing bugs. But they can be a big risk if left open.&lt;/p&gt;

&lt;p&gt;Search engines can accidentally index them. Attackers can use them to study our code. Sometimes, these sites even contain real data used for testing.&lt;br&gt;
To stop this, we can use the &lt;a href="https://www.drupal.org/project/shield" rel="noopener noreferrer"&gt;Shield module&lt;/a&gt;. It adds a basic username and password prompt that appears before the Drupal site loads. This is a simple but effective wall that keeps outsiders away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxprbj4wkhqwehhx553m6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxprbj4wkhqwehhx553m6.png" alt=" " width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set a simple username and password to block public access to staging or test sites.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5cbmq5q0p075p3lp3fk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft5cbmq5q0p075p3lp3fk.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdypchtvwrud3f51n7t5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdypchtvwrud3f51n7t5.png" alt=" " width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Apply a Strict Content Security Policy (CSP)&lt;/strong&gt;&lt;br&gt;
Even though we might have installed Security Kit (SecKit) earlier, the most powerful feature &lt;strong&gt;Content Security Policy (CSP)&lt;/strong&gt; comes into full play during deployment.&lt;br&gt;
CSP is a set of rules sent from the server to the browser. These rules tell the browser which sources are allowed to load content like scripts, styles, images, fonts, or iframes. If something tries to load from an unapproved location, the browser blocks it automatically.&lt;br&gt;
For example, if someone tries to inject a malicious JavaScript file from an unknown website, CSP stops it from running. It’s like telling the browser, “Only accept food from this kitchen, not from the stranger at the door.”&lt;/p&gt;

&lt;p&gt;To make CSP effective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List only the domains you truly need (your CDN, analytics tool, font provider, etc.)&lt;/li&gt;
&lt;li&gt;Avoid using unsafe-inline or wildcards unless absolutely required&lt;/li&gt;
&lt;li&gt;Test the policy using report-only mode before enforcing it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A solid CSP setup is one of the best ways to block Cross-Site Scripting (XSS) attacks, which remain a major threat across the web.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Secure the Server Environment&lt;/strong&gt;&lt;br&gt;
Beyond Drupal itself, the server plays a huge role in the site’s overall safety. If the server is not properly configured, even the cleanest code can be compromised.&lt;/p&gt;

&lt;p&gt;Here are key areas to harden:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;File and Folder Permissions&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files on the server should be readable by the web server, but not writable by the public. Directories that don’t need to execute scripts should block execution. Avoid overly permissive settings like 777, which allow anyone to read, write, and execute.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;A safe setup might look like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files: 644&lt;/li&gt;
&lt;li&gt;Directories: 755&lt;/li&gt;
&lt;li&gt;No PHP execution allowed in /sites/default/files&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Use HTTPS Everywhere&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every page should be served over HTTPS. This encrypts the connection between the server and the user’s browser, keeping data safe in transit. It also helps build trust, since modern browsers now mark HTTP-only sites as “Not Secure.”&lt;/li&gt;
&lt;li&gt;Redirect all HTTP traffic to HTTPS automatically using your .htaccess file or web server configuration.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Keep Software Updated&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security patches are released regularly for PHP, database engines, and the operating system itself. Running old versions can leave open doors that attackers already know how to exploit.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Backup Strategy&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disasters happen : servers crash, databases get corrupted, or someone deletes content by mistake. That’s why backups are not optional.&lt;/li&gt;
&lt;li&gt;Set up regular automated backups that include:

&lt;ul&gt;
&lt;li&gt;Database dumps&lt;/li&gt;
&lt;li&gt;Public and private files&lt;/li&gt;
&lt;li&gt;Configurations&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Test restoring from backups occasionally so you know they actually work.&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;When a Drupal site is deployed, it becomes visible to the world but also vulnerable to it. What we do during this phase has long-term effects on the stability, safety, and success of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Start security early with tools like SecKit&lt;/li&gt;
&lt;li&gt;Automate checks using CI/CD pipelines&lt;/li&gt;
&lt;li&gt;Protect dev environments using Shield&lt;/li&gt;
&lt;li&gt;Use CSP and headers to reduce browser-side attacks&lt;/li&gt;
&lt;li&gt;Regularly patch and back up the server&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Stats
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;71% of breaches in enterprise websites are caused by vulnerabilities that had patches available. Source: &lt;a href="https://www.verizon.com/business/resources/reports/dbir/2023/" rel="noopener noreferrer"&gt;Verizon 2023 Data Breach Investigations Report&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drupal has over 40,000 contributed modules, and more than 1,000 have security advisories.Source: &lt;a href="https://www.drupal.org/security" rel="noopener noreferrer"&gt;Drupal.org Security Advisories&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  7. Interesting Facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The U.S. White House, NASA, and European Commission websites have used Drupal due to its strong security features.&lt;/li&gt;
&lt;li&gt;The Security Kit (SecKit) module allows configuration of over 30 security headers directly from the admin UI.&lt;/li&gt;
&lt;li&gt;Clickjacking prevention using X-Frame-Options is one of the simplest yet most overlooked browser defenses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1 : Can security headers conflict with frontend features?&lt;/strong&gt;&lt;br&gt;
A: Yes, misconfigured CSP or X-Frame-Options can block legitimate scripts or iframes. Use “report-only” mode to test policies safely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: What should I use to hide staging sites?&lt;/strong&gt;&lt;br&gt;
A: The Shield module is ideal for adding simple HTTP authentication to non-public environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: How often should I update modules and server packages?&lt;/strong&gt;&lt;br&gt;
A: At least monthly or whenever a security update is released.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Conclusion
&lt;/h2&gt;

&lt;p&gt;Security isn't a one-time task, it's something we keep working on as the site grows. At AddWeb Solution, we believe that safety should be part of every step, starting from the very first idea all the way to live deployment and beyond. By thinking about security while planning the site, checking code automatically during reviews, and setting up a strong and safe live environment, we don’t leave room for guesswork. Each phase builds a stronger layer of protection. When these layers work together, the site becomes much harder to attack and much easier to trust.&lt;br&gt;
This kind of proactive approach not only protects data and users, but also helps build lasting confidence in the brand behind the website. That’s what sets enterprise Drupal projects apart and why security should always be part of the journey, not just the destination.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“It’s not the will to win that matters, everyone has that. It’s the will to prepare to win that matters..” - Paul “Bear” Bryant&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;About the Author:&lt;em&gt;Balasaranya Varadhalingam, Software Engineer (Drupal) at &lt;a href="https://www.addwebsolution.com/our-capabilities/drupal-development-services" rel="noopener noreferrer"&gt;AddWebSolution&lt;/a&gt;, specializes in building secure and scalable Drupal solutions for enterprise clients.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>drupal</category>
      <category>websecurity</category>
      <category>enterprisedrupal</category>
      <category>devsecops</category>
    </item>
  </channel>
</rss>
