Explore Developer Center's New Chatbot! MongoDB AI Chatbot can be accessed at the top of your navigation to answer all your MongoDB questions.

Learn why MongoDB was selected as a leader in the 2024 Gartner® Magic Quadrant™
MongoDB Developer
MongoDB
plus
Sign in to follow topics
MongoDB Developer Center
chevron-right
Developer Topics
chevron-right
Products
chevron-right
MongoDB
chevron-right

Building REST APIs With API Platform and MongoDB

Aasawari Sahasrabuddhe9 min read • Published Jan 09, 2025 • Updated Jan 14, 2025
MongoDBPHP
FULL APPLICATION
Facebook Icontwitter iconlinkedin icon
Rate this article
star-empty
star-empty
star-empty
star-empty
star-empty
Today's web applications rely on large datasets and require real-time database interaction. Developers often face the challenge of efficiently managing and scaling CRUD (Create, Read, Update, Delete) operations while maintaining flexibility, performance, and security. However, building a standard API often comes with significant challenges: documenting endpoints, mapping them to the database, handling data transformation, and ensuring validation—all while maintaining performance, scalability, and security.
Building an application with API Platform provides a robust, flexible, and user-friendly solution for creating modern APIs. When seamlessly integrated with MongoDB, it empowers developers to efficiently handle CRUD operations while maintaining scalability and ease of use.
In this tutorial, we will use API Platform with Symfony to build REST APIs that perform CRUD operations on the MongoDB database. To connect the application with MongoDB, we will use MongoDB Atlas.

Prerequisites

  • A free Atlas cluster—register to create your first free cluster
  • Docker installed

Creating a Symfony project

To start with the Symfony project using API Platform, follow the steps outlined below:
  1. Create the template project, which generates all the necessary files for you. To do so, generate a GitHub repository with your choice of name using the api-platform repository template. You can refer to the screenshot below to fill in the entries.
Image showing steps to clone the GitHUb repository Figure 1: Image showing steps to clone the GitHUb repository 2. Once the repository is created, you can clone it using the command below.
1git clone <URL to your repository>
Once you have the code on your local machine, we will edit the application to connect with MongoDB.
To learn more, you can follow the steps mentioned in the Getting Started with API Platform documentation for Symfony.
In the next section, we’ll understand how to connect your application to the MongoDB Atlas cluster and perform the CRUD operations on the collections inside the cluster.

Connecting your application with MongoDB Atlas

After you have your sample application running correctly, the next step is to connect with MongoDB Atlas. To do so, we will first need to create a free Atlas cluster and get the connection string.
To get the connection string, click on Connect and then select the appropriate driver with the correct version. You will see a screen like the one below; copy the connection string and keep it safe with you.
Image showing steps to get the Atlas connection String Figure 2: Image showing steps to get the Atlas connection String
Once you have the connection string, the next step is to update the Dockerfile and install the PHP extensions. The Dockerfile to install the extensions is available inside the api/ folder.
Update the file with the code below to install PHP extensions.
  1. Add the below code changes to the Dockerfile you will need to update:
1RUN apt-get update && apt-get install --no-install-recommends -y \
2 libcurl4-openssl-dev \
3 libssl-dev \
4 && pecl install mongodb \
5 && docker-php-ext-enable mongodb
After the update, run the command from the root folder of the project to install the extensions. In this case, go to <location where git clone was done/repository name>.
1docker compose build --no-cache
  1. Update the .env file with the connection string and database name.
1MONGODB_URL=<Atlas URI>
2MONGODB_DB=Test
  1. Update the compose.yaml file with the connection string as:
1services:
2 php:
3 image: ${IMAGES_PREFIX:-}app-php
4 depends_on:
5 - pwa
6 environment:
7 MONGODB_URL: <Atlas URI>
  1. After the extensions are installed, we need to start the containers and install the ODM bundle.
1docker compose up --wait
Once the containers are up and ready, execute the below commands to install the ODM bundle.
1docker compose exec php \
2 composer require doctrine/mongodb-odm-bundle api-platform/doctrine-odm
The mongodb-odm-bundle is a bundle (a modular package of code) that integrates MongoDB ODM into Symfony. This library provides a PHP object mapping functionality for MongoDB.

Performing CRUD operations

Once you are all set with making the MongoDB Atlas connections, the next step is to create REST APIs to perform CRUD operations.
In this tutorial, we are using a simple collection named Restaurants, which will have the following field values:
1{
2 "name": "The Gourmet Spot",
3 "address": {
4 "building": "123",
5 "street": "Elm Street",
6 "zipcode": "12345"
7 },
8 "borough": "Manhattan",
9 "cuisine": "Italian"
10}
Once the document structure is decided, the next step is to create the Document and the Controller class.
The Document class is created at api/src/Document/Restaurant.php.
Copy the below code in the Restaurant.php class. This class has all the field values with all the getter and setter methods.
1<?php
2
3namespace App\Document;
4
5use ApiPlatform\Metadata\ApiResource;
6use App\Repository\RestaurantRepository;
7use Doctrine\ODM\MongoDB\Mapping\Annotations\Document;
8use Doctrine\ODM\MongoDB\Mapping\Annotations\Field;
9use Doctrine\ODM\MongoDB\Mapping\Annotations\Id;
10
11#[ApiResource]
12#[Document(collection: 'restaurants')]
13class Restaurant
14{
15 #[Id]
16 public string $id;
17
18 #[Field]
19 public string $name;
20
21 #[Field]
22 public array $address;
23
24 #[Field]
25 public string $borough;
26
27 #[Field]
28 public string $cuisine;
29}
The Address.php will look like:
1<?php
2
3namespace App\Document;
4
5use Doctrine\ODM\MongoDB\Mapping\Annotations\EmbeddedDocument;
6use Doctrine\ODM\MongoDB\Mapping\Annotations\Field;
7
8#[EmbeddedDocument]
9class Address
10{
11 #[Field]
12 public string $building;
13
14 #[Field]
15 public string $street;
16
17 #[Field]
18 public string $zipcode;
19}
Once the code is all set, run the below command to run the complete application.
1HTTP_PORT=8080 HTTPS_PORT=8443 docker-compose up

Testing the CRUD operations

The next step is to access the access APIs that have been created. The below URI takes you to the swagger of the API platform to test the REST API calls.
1https://localhost:8443/docs
API Platform natively supports the Open API (formerly Swagger) API documentation format. It also integrates a customized version of Swagger UI, a nice tool to display the API documentation in a user-friendly way.
This page at the below URI will look like the following:
Image showing screenshot for the API platform swagger Figure 3: Image showing screenshot for the API platform swagger
Let's test these REST APIs.

Create

To test the CREATE API, go to the POST method and place the JSON as:
1{
2 "name": "The Gourmet Spot",
3 "address": {
4 "building": "123",
5 "street": "Elm Street",
6 "zipcode": "12345"
7 },
8 "borough": "Manhattan",
9 "cuisine": "Italian"
10}
Click on “Try it out” and you will see the below document has been inserted into the collection:
Screenshot of the swagger representing the POST request Figure 4: Screenshot of the swagger representing the POST request
To verify, navigate to the Atlas cluster and check if the data has been inserted in the test.Restaurants collection. The screenshot below shows that the data has been inserted into the database.
Screenshot of Atlas UI representing that data has been inserted Figure 5: Screenshot of Atlas UI representing that data has been inserted

Read

To get all the documents from the collection, you can simply use the GET API to get all the documents. To get a specific document from the collection, run the GET API as specified.
Screenshot of swagger representing GET request Figure 6: Screenshot of swagger representing GET request

Update

There are two operations to update an existing document. PUT replaces the full document and PATCH will update only the properties that are sent. Usually, only PATCH is used, which is why the PUT operation is disabled by default. For more information, you can look into the documentation for API Platform Operations.
To test this API, we have updated the above JSON document as:
1{
2 "name": "The Gourmet Spot",
3 "address": {
4 "building": "123",
5 "street": "Elm Street",
6 "zipcode": "12345"
7 },
8 "borough": "New York",
9 "cuisine": "Spanish"
10}
The API results in:
Screenshot of swagger representing PUT request Figure 7: Screenshot of swagger representing PUT request
The document is also updated in the Atlas cluster.
Screenshot of Atlas UI representing the update of the document Figure 8: Screenshot of Atlas UI representing the update of the document

Delete

Finally, to delete specific restaurant information with an _id, we run the API call as:
Screenshot of swagger representing DELETE request Figure 9: Screenshot of swagger representing DELETE request
The above tests cover the basic CRUD operations: Create, Read, Update, Delete. But the API platform goes way beyond that. It has a ton of features that let you build more powerful and dynamic APIs with minimal work.
Let’s see each of these in detail in the next section.

Beyond CRUD with API Platform

The API platform simplifies creating and enforcing validations so your data integrity rules are applied consistently without having to write a lot of custom code. And it’s great at advanced search too, so you can execute complex queries with ease. It also allows you to apply filters, perform regex validations, and much more.

Performing validations on the field

If you wish to put validations on the field to be required values, we set the field values as #[Assert\NotBlank] before the field values. For example, in the code below, we have marked the name, borough, and cuisine as the required values.
1class Restaurant
2{
3 #[Id]
4 public string $id;
5
6 #[Field]
7 #[Assert\NotBlank]
8 #[Assert\Length(max: 255)]
9 public string $name;
10
11 #[EmbedOne(targetDocument: Address::class)]
12 #[Assert\Valid]
13 public ?Address $address;
14
15 #[Field]
16 #[Assert\NotBlank]
17 public string $borough;
18
19 #[Field]
20 #[Assert\NotBlank]
21 public string $cuisine;
22}
As a result, if a request is sent with any of the missing values, an HTTP error will be returned. The screenshot below shows an example of a name missing from the POST request.
Screenshot of swagger representing POST request with required field as empty Figure 10: Screenshot of swagger representing POST request with required field as empty
We need to add the code to the Validators to add this validation. This code should be available inside the api/src/Validator/Constraints/ folder.
1<?php
2
3namespace App\Validator\Constraints;
4
5use Symfony\Component\Validator\Constraint;
6use Symfony\Component\Validator\ConstraintValidator;
7
8final class MinimalPropertiesValidator extends ConstraintValidator
9{
10 public function validate($value, Constraint $constraint): void
11 {
12 if (array_diff(['name', 'cuisine', 'bourough'], $value)) {
13 $this->context->buildViolation($constraint->message)->addViolation();
14 }
15 }
16}
And:
1<?php
2
3namespace App\Validator\Constraints;
4
5use Symfony\Component\Validator\Constraint;
6
7#[Attribute]
8class MinimalProperties extends Constraint
9{
10 public $message = 'The product must have the minimal properties required ("name", "cuisine", "bourough")';
11}
This code defines a custom validation logic in Symfony. The MinimalPropertiesValidator class is responsible for validating that an array contains the required keys: name, cuisine, and borough.
The validate method checks if any of these keys are missing using array_diff, and if the validation fails, it triggers a violation with an error message.
The MinimalProperties class acts as the custom constraint, with its message property holding the error text displayed upon failure.
Similarly, if you wish to add the validator for the ZIP Code provided in the Address field values, add the below code in the api/src/Validator/Constraints/ folder as:
1<?php
2
3namespace App\Validator\Constraints;
4
5use Symfony\Component\Validator\Constraint;
6use Symfony\Component\Validator\ConstraintValidator;
7
8final class ValidZipcodeValidator extends ConstraintValidator
9{
10 public function validate($value, Constraint $constraint): void
11{
12 if (!$constraint instanceof ValidZipCode) {
13 throw new InvalidArgumentException(sprintf('Expected instance of %s, got %s.', ValidZipCode::class, get_class($constraint)));}
14
15 if (!preg_match('/^[0-9]{5}$/', $value)) {
16 $this->context->buildViolation($constraint->message)
17 ->setParameter('{{ value }}', $value)
18 ->addViolation();}
19 }
20}
And:
1<?php
2
3namespace App\Validator\Constraints;
4
5use Symfony\Component\Validator\Constraint;
6
7#[Attribute]
8class ValidZipCode extends Constraint
9{
10 public $message = 'The zipcode "{{ value }}" is not valid. It must be exactly 5 digits.';
11}
In the above code, ValidZipcodeValidator creates the validation that the ZIP Code can only be numeric and only five characters will be allowed. For any other ZIP Code, it should throw the error as mentioned in the ValidZipCode class.
You also need to declare the function in the Address.php as:
1 #[Field]
2 #[ValidZipCode]
3 public string $zipcode;
To test this, we can send the POST request as:
1{
2 "name": "Dim Sum Express",
3 "address": {
4 "building": "404",
5 "street": "Chinatown Blvd",
6 "zipcode": "abcnn"
7 },
8 "borough": "Manhattan",
9 "cuisine": "Chinese"
10 }
The above request should result in an HTTP error with code 422 and a message saying:
1The zipcode \"abcnn\" is not valid. It must be exactly 5 digits
The below screenshot displays the error message with the POST request.
Screenshot of swagger representing POST request invalid ZipCode value Figure 11: Screenshot of swagger representing POST request invalid ZipCode value

Adding filters using API Platform

The API platform allows you to apply filters and sort criteria on the collections. The search filter supports exact, partial, start, end, and word_start matching strategies. You can read more about filters from the API platform documentation on search filters.
In our case, we will apply search filters to the name and the cuisine fields using the below code inside the Document class.
1#[ApiFilter(
2 SearchFilter::class,
3 properties: [
4 'name' => 'ipartial', The "ipartial" strategy will use a case-insensitive partial match
5 'cuisine' => 'exact', The "exact" strategy will use an exact match
6 ])
7]
As the comment suggests, we have a partial filter that’s case-sensitive for the name, and an exact match filter on the cuisine fields.
To test this, we have sample data already being stored inside the collection as:
1db.restaurants.find()
2[
3 {
4 _id: ObjectId('6750aa7acda8e992af0c97b4'),
5 name: 'The Gourmet Spot',
6 address: { building: '123', street: 'Elm Street', zipcode: '12345' },
7 borough: 'Manhattan',
8 cuisine: 'Italian'
9 },
10 {
11 _id: ObjectId('6750b8406868c9f4fb012be5'),
12 name: 'Burger Bliss',
13 address: { building: '789', street: 'Main Street', zipcode: '54321' },
14 borough: 'Queens',
15 cuisine: 'American'
16 },
17 {
18 _id: ObjectId('6750b8516868c9f4fb012be8'),
19 name: 'Curry Delight',
20 address: { building: '202', street: 'Spice Lane', zipcode: '45678' },
21 borough: 'Staten Island',
22 cuisine: 'Indian'
23 },
24 {
25 _id: ObjectId('6750b85e6868c9f4fb012beb'),
26 name: 'Pasta Paradise',
27 address: { building: '303', street: 'Olive Way', zipcode: '11223' },
28 borough: 'Manhattan',
29 cuisine: 'Italian'
30 },
31 {
32 _id: ObjectId('6750b86e6868c9f4fb012bee'),
33 name: 'Pizza Kingdom',
34 address: { building: '606', street: 'Pizza Lane', zipcode: '77889' },
35 borough: 'Queens',
36 cuisine: 'Italian'
37 }
38]
Now, to apply the search filter, we send the GET request with the cuisine name as Italian and we should see all restaurant with cuisine: 'Italian'.
Screenshot of swagger representing GET request with filtered values only for Cuisine as Italian Figure 12: Screenshot of swagger representing GET request with filtered values only for Cuisine as Italian
Similarly, we send a GET request with a partial name as "name": "Pasta...", and we should have a restaurant with the name “Pasta Paradise.”
Screenshot of swagger representing GET request with partial name as only Pasta Figure 13: Screenshot of swagger representing GET request with partial name as only Pasta
You can create more API calls based on the requirement by extending the code available in the GitHub repository and following the API Platform documentation.

Conclusion

In conclusion, we've seen how API Platform lets you quickly create a REST API to perform CRUD operations on a MongoDB database. This framework also lets you add features to the API, such as data validation and query filters, while keeping the code highly comprehensible and scalable. Developers can work in a local environment using Docker and Atlas.
The tutorial also demonstrates how to establish the connection, configure the environment, and perform basic CRUD operations using a simple example, highlighting the flexibility and ease of working with Symfony's API Platform.
To explore more, consider diving into the advanced features of API Platform, optimizing MongoDB queries, or experimenting with additional CRUD operations to meet your specific application needs. If you wish to learn more, you can visit the documentation for API Platform and MongoDB.
For further questions, please reach out to the MongoDB Community Forum, and to learn more, explore the MongoDB Developer Center for more interesting articles.
Top Comments in Forums
There are no comments on this article yet.
Start the Conversation

Facebook Icontwitter iconlinkedin icon
Rate this article
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Article

The Cost of Not Knowing MongoDB


Nov 11, 2024 | 23 min read
Tutorial

Scaling for Demand: Deploying Python Applications Using MongoDB Atlas on Azure App Service


Apr 02, 2024 | 12 min read
Code Example

A Spotify Song and Playlist Recommendation Engine


Nov 13, 2023 | 6 min read
Tutorial

How to use MongoDB Client-Side Field Level Encryption (CSFLE) with Node.js


Sep 23, 2022 | 12 min read
Table of Contents