How to create a REST API server with Rust and MongoDB
A REST API server is one of the most used ways of providing services, connections and information to different platforms that do not have a direct connection to a database, not to mention that the construction of a single service can provide data to several platforms at the same time, here its importance and versatility.
But how is this accomplished in Rust. We will go by parts and specifying each command and code snippet to achieve this. It should be noted that this code is cross-platform, so it will work just as well on Windows as Linux, with their respective annotations when configuring the path.
- Preconfiguration:
The first thing will be to create the project repository, in the terminal or command line, we must place ourselves in the folder where we want to have our code, in my case it will be one that creates called “api_rest_server”, inside a repository on GitHub, but this folder will be in anywhere on the computer, is at ease. The command that we will execute will be:
cargo init
Once the repository of our project is created, we should look like this, with these files:
The only one that may or may not be there, will be the “.env” file, because this is environment configuration file, if this file has not been generated, we will create it, this is achieved from any code editor, we generate a new file and simply name it “.env”.
Subsequently, we go to the “Cargo.toml” file, since we will declare our dependencies in it, these dependencies are the libraries that we will use in our entire project. For reference, if you have used Node.js or React.js, imagine that this file is the same as the “package.json” file of those frameworks.
Inside “Cargo.toml” we will write the following in the dependencies section (“ [ dependencies ] “).
[dependencies]
mongodb = "0.9.2"
actix-web = "2"
actix-rt = "1"
bson = "0.14.0"
serde = "1.0.103"
futures = "0.3.4"
env_logger = "0.7"
dotenv = "0.15.0"
actix-cors = "0.2.0"
serde_json = "1.0"
The result will be something like this, where we will have the necessary libraries:
Then, we will go to the “src” folder and inside of that, we generate 2 folders more, one called “api_router” and another called “api_service”, inside these, we must create a file for each called “mod.rs”, this It refers to the fact that they are modules of our main.rs file and that later we will need to provide routes and services to our server.
Finally, in the settings section, we will go to the previously created or generated “.env” file, inside this, we will declare some fixed values of our server. It is worth mentioning that in this file the variables that are necessary can be declared, in our case they will be the following and the result will look like this:
DATABASE_URL = mongodb://localhost:27017
DATABASE_NAME = test
USER_COLLECTION_NAME = test
SERVER_URL = 0.0.0.0:4000
- Programming of the server:
We begin by declaring all of our main.rs, in this file, let’s start with the imports and references to our other 2 modules, as well as creating the constructors and their implementation of our API SERIVE, which will count all our queries to the database and must be treated as a structure definite, due to your answers. These builders work like all statements of the functions in a class in a language like Java, only in Rust, packed it into a structure and we reference on the server.
Later, we will write our “main()” function, remember that this function is the first one to run in any Rust compilation, so we must name this function with this name and then not repeat it. Inside this function, we will only take care of 3 things:
- Extract all the variables from the .env file.
- Declare the methods, routes and queries of our API SERVICE and API ROUTER modules
- Declare and start the server.
Let’s highlight a couple of points:
- We need to reference “actix_rt” to our “main” because it inherits directly from this library.
- Our result will be “std::io::Result <()>” because this would be like returning a void, only without a return at the end, we are leaving the response function listening until we terminate the server.
With this we would end our main function and we can advance to our SERVICE API, the first thing we will do is the corresponding imports, as in the main, but we will also declare the structure that the documents will take in Mongo , this in order to have only the fields that we need (This is like creating a Schema in Node.js) , we will clone the collection to make use of the queries and we will create a function to convert the json strings of the request to documents in Mongo.
Later, we create the functions corresponding to a CRUD, plus an extra function where we will insert a specific query to search based on the author. For this, we must make use of an implementation of the collection that we cloned, which we call “ApiService”.
Finally, we will create the file corresponding to API ROUTER, this will have the routes that will connect to the server and the response of that route. Each function will have its necessary parameters, the conversion of the query response and the http 200 or http 500 response that it will return in the response. Let’s start with the imports and the first 2 routes, which correspond to two Finds, one to extract all the documents from the collection and the other to bring the documents by author.
On the other hand, we declare the routes corresponding to an Update, a Delete and a Create to complete the CRUD that we are creating.
Now, finally, we will only add those functions to the start function that we call in the main, we pass these created functions to our service.
- Server execution:
In the same path where we created the project, where at the beginning we created the project with cargo init, we must type 2 more commands to compile and execute the project:
cargo build
and later:
cargo run
If everything is compiled and executed correctly, we will have our server listening on “localhost: 4000”.
- Examples in Postman:
Next, we will test each route to find out what data it takes as input and what data it outputs.
/get-all
This route does not take any input parameters, since by default it makes a find to the table that we specify in our SERVICE API. It will return an array of objects, where each one will have the structure that we specif, in our case, we only obtain id, title and author.
/get-by/<Author>
In the case of the path to search based on the author, this does require a parameter that is the name of the author, there is a note to make here, and that is that this parameter is not a Query Param, it is simply the path with a “/”. The answer will be an array of objects with the same structure as the previous example.
/add
In the case of the route to create new records, it will take as input a Json RAW that will pass through the body of the request, just as you can see in the image, this Json will only have the title and the author. For the case of the answer, if everything went correct, it will return the new Object Id generated in Mongo DB.
/update/<id>
The Update is the path that requires more data, first of all, in the body of the request we must pass in Json RAW as in the previous example, but also we must put the Mongo Object Id of the record to modify in the URL as in the search by author. In the case of the answer, we will only return the number of modified documents.
/delete
Finally, the case of our Delete, this receives a Json RAW which it will use to compare, of course the title is important more than anything. The answer will be the number of documents deleted.
- Concussion:
With the completion of this practice, we can conclude that Rust is an ideal language for tasks where the execution loads are closer to the processor and so, exploit the full potential of this compiled language, an example of these loads or processes is the REST API Server that we have just achieved, a server that, based on the architecture and structure that we choose, is easily scalable to perform more complex actions and so, continue to take advantage of this programming language.
- Extra information and thanks
The full repository of this practice you can find it here.
For your attention… thank you so much.