REST API With Scala Play Framework and Reactive Mongo
The basics of building a REST API with the Scala/Play Framework with Reactive mongo.
Introduction
In this post, I’m going to demonstrate how to build an asynchronous & non-blocking REST application using the Play framework and reactive mongo plugin. For the sake of simplicity, in this story, we’ll create a basic API that allows us to perform the basic CRUD operations for the data stored in a mongo database.
Prerequisite
The post is accessible for beginners. However, Scala and Play framework fundamentals are required. Before going any further, please make sure that you have the following tools installed on your local machine.
- JVM 14
- Scala 2.13.3
- Sbt 1.3.13
- Docker (Optional)
You can set up a local mongo db server using to following commands:
> mkdir mydata
> docker pull mongo:latest
> docker run --name local-mongodb -d -p 27017:27017 -v ~/data:/data/db mongo
The project setup
The first part consists of creating the project and add required dependencies.
Seed the project:
The first step is to create our project skeleton. This can be easily done using this sbt command:
> sbt new playframework/play-scala-seed.g8
You’ll be asked to fill in some basic information about the project such as the name of the package and the organization.
This template generates a Play Scala projectname [play-scala-seed]: movie-store
organization [com.example]:Template applied in ./movie-store
By the time I’m writing this, the project is generated with Play 2.8. If you’re somewhere in the future, you might get a new version of Play. If so, please consider upgrading the other dependencies as well.
If everything goes well, you should be able to start the application with this command.
> sbt run
Add dependencies:
The reactive mongo plugin for Play 2.8 can be enabled by adding the following dependencies:
The mongo driver will be resolved automatically as transitive dependency.
To enable dependency injection for reactive mongo module. Add the following line into the application.conf.
Configure database access:
The last step is to configure the database access.
The API
Now that our project is ready, we’ll move on to the next step that consists of creating the models, repositories, and controllers. I’m going to keep the example simple as much as possible but it should be good enough for our purposes.
The model:
As you can see, we have a basic case class
that contains the definition of a movie and a companion object
that contains the implicit
JSON/BJSON serializers.
For JSON serialization we’re using automated mapping. Basically, the Json.format[Movie]
macro will inspect the movie case class fields and produce a JSON. On the other hand, for external types, you should provide their serializers as implicit
just like the case for DateTime
.
For BSON, however, we’re implementing our custom serializer.
The repository:
So, this is our initial version of the movie repository. Basically, it injects the execution context and the reactive mongo api. Also, it contains a helpers function that returns a Future
ofBSONCollection
.
The `collection` is a function to avoid potential problems in development with play auto reloading.
Once the repository is ready, we can start by adding some basic queries.
The find
method takes two arguments, the selector and the projection. In nutshell, the selector is used to match specific documents and the projector is used to project-specific fields on the documents. In our case, we want to keep things simple and stick with defaults.
The find method returns a query builder which means the query is therefore not performed yet. It allows you to add options to the query, like a sorting ordering.
You can learn more about the find in the mongo documentation.
Now, you can see the write queries. The insert insert
method returns an InsertBuilder
instance which you can use to insert one
or many
documents. Same thing for update and delete methods which return an UpdateBuilder
and DeleteBuilder
respectively.
The controller
Now, we reached the last part. We’ll create the endpoints to expose the actions for the movies repository.
Let’s start by creating the controller:
Now, we’ll create the two endpoints responsible of reading data:
We have two endpoints, the first one will return the movie list and the second one will parse the given id and return the associated movie if it’s found.
Besides validating the id passed in an argument like the previous code. Also, we check if the json is valid by using the validate helper in the request body.
Thanks to the json serialization macro created in the previous section, the Scala object can be serialized implicitly from json and vise-versa.
The last part is the bind the controller methods to their routes.
Testing the API
Now that our application is ready we can make simple tests with `cURL`.
Let’s start by creating a movie.
> curl --verbose --header "Content-Type: application/json" \
--request POST \
--data '{ "title":"My favorite movie", "description":"My favorite movie description" }' \
http://localhost:9000/movies
If everything is working, you should get a similar result.
< HTTP/1.1 201 Created
< Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
< X-Frame-Options: DENY
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Permitted-Cross-Domain-Policies: master-only
< Date: Sun, 10 Jan 2021 12:25:06 GMT
< Content-Type: application/json
< Content-Length: 75
{
"title": "My favorite movie",
"description": "My favorite movie description"
}*
Now with a `GET` request:
> curl --verbose --request GET http://localhost:9000/movies
Again, you should get a similar result.
< HTTP/1.1 200 OK
< Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
< X-Frame-Options: DENY
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Permitted-Cross-Domain-Policies: master-only
< Date: Sun, 10 Jan 2021 12:47:34 GMT
< Content-Type: application/json
< Content-Length: 213
[
{
"_id": {
"$oid": "5ffaf5b787901a6769f585f8"
},
"_creationDate": "2021-01-10T13:40:23.359+01:00",
"_updateDate": "2021-01-10T13:40:23.359+01:00",
"title": "My favorite movie",
"description": "My favorite movie description"
}
]*
Conclusion
In this post, we saw a basic example of creating a restful API with Play, on the top of a mongo database. You can find the code solution in my Github. Thank you.