¿Cómo crear un servidor API REST con Rust y MongoDB?

Alejandro Aldana
7 min readJun 21, 2021

Un servidor API REST es una de las formas más usadas de proveer de servicios, conexiones e información a distintas plataformas que no cuentan con una conexión directa a una base de datos, sin mencionar que la construcción de 1 solo servicio, puede proveer de datos a varias plataformas al mismo tiempo, de aquí su importancia y versatilidad.

Pero, como se logra esto en Rust. Iremos por partes y especificando cada comando y fragmento de código para lograr esto. Cabe destacar que este código es multiplataforma, por lo que funcionara igual de bien en Windows como Linux, con sus respectivas anotaciones a la hora de configurar los path.

  • Pre configuración:

Lo primero será crear el repositorio del proyecto, en la terminal o línea de comandos, debemos colocarnos en la carpeta donde queremos tener nuestro código, en mi caso será una que cree llamada “api_rest_server” dentro de un repositorio en GitHub, pero esta carpeta podrá ir a gusto en cualquier parte del ordenador. El comando que ejecutaremos será:

cargo init
Nota: el intérprete del terminal usado es “Hyper” y no CMD de Windows

Una vez creado el repositorio de nuestro proyecto, debemos tener un aspecto como este, con estos archivos:

Nota: el editor de código usado será Sublime Text 3, este es compatible con “anaconda_rust”, un complemento ideal para este proyecto.

El único que puede estar o no, será el archivo “.env”, debido a que este es de configuración de entorno, si no nos ha generado este archivo, lo crearemos, esto se logra desde cualquier editor de código, generamos un nuevo archivo y lo nombramos simplemente “.env”.

Posteriormente, nos dirigimos al archivo “Cargo.toml”, ya que declararemos nuestras dependencias en este, estas dependencias son las librerías que usaremos en todo nuestro proyecto. Como referencia, si haz usado Node.js o React.js, imagina que este archivo es igual al archivo “package.json” de esos frameworks.

Dentro de “Cargo.toml” escribiremos lo siguiente en la sección de dependencias (“[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"

El resultado será algo así, donde tendremos las librerías necesarias:

Ejemplo de Cargo.toml

A continuación, nos dirigiremos a la carpeta “src” y dentro de esta generaremos 2 carpetas más, una llamada “api_router” y otra llamada “api_service”, dentro de estas debemos crear un archivo por cada una llamado “mod.rs”, esto hace referencia a que son módulos de nuestro archivo main.rs y que mas tarde necesitaremos para proveer de rutas y servicios a nuestro servidor.

Por último, en el apartado de las configuraciones, iremos al archivo previamente creado o generado .env, dentro de este declararemos algunos valores fijos de nuestro servidor. Cabe mencionar que en este archivo se pueden declarar las variables que sean necesarias, en nuestro caso serán las siguientes y el resultado se verá así:

DATABASE_URL         = mongodb://localhost:27017
DATABASE_NAME = test
USER_COLLECTION_NAME = test
SERVER_URL = 0.0.0.0:4000
Ejemplo de archivo .env
  • Programación del servidor:

Empezaremos por declarar todo nuestro main.rs, dentro de este, comencemos con las importaciones y las referencias a nuestros otros 2 módulos, así como crear los constructores y su implementación de nuestro API SERIVE, el cual contendrá todas nuestras consultas a la base de datos y hay que tratarlo como una estructura definida, debido a sus respuestas. Estos constructores funcionan como la declaración de todas las funciones en una clase en un lenguaje como Java, solo que en Rust, empaquetamos eso en una estructura y la referenciamos en el servidor.

Primera parte de main.rs

Posteriormente, escribiremos nuestra función “main()”, recordemos que esta función es la primera en ejecución en cualquier compilación de Rust, por lo que debemos bautizar con este nombre a esta función y posteriormente, no repetirlo. Dentro de esta función solamente nos encargaremos de 3 cosas, extraer todas las variables del archivo .env, declarar los métodos, rutas y consultas de nuestros módulos API SERVICE y API ROUTER, y declarar e iniciar el servidor.

Segunda parte de main.rs

Remarquemos un par de puntos:

  • Necesitamos la referencia de “actix_rt” hacia nuestro “main”, debido a que este hereda directamente de esta librería.
  • Nuestro resultado será “std::io::Result<()>” por que esto seria como regresar un void, solo que sin un return al final, estamos dejando la función de respuesta escuchando hasta que finalicemos el servidor.
Código completo de main.rs

Con esto finalizaríamos nuestra función main y podremos avanzar a nuestro API SERVICE, lo primero que realizaremos es las importaciones correspondientes, como en el main, pero además declararemos la estructura que tomaran los documentos en Mongo, esto con el fin de tener solo los campos que necesitamos (Esto es como crear un Schema en Node.js), clonaremos la colección para hacer uso de los queries y crearemos una función para convertir los json string de la petición a documentos en Mongo.

Primera parte de Api Service

Posteriormente, creamos las funciones correspondientes a un CRUD más una función extra donde insertaremos un query especifico para buscar con base en el autor. Para esto, debemos hacer uso de una implementación de la colección que clonamos, a la cual llamamos “ApiService”.

Segunda Parte de Api Service
Código completo de api_service/mod.rs

Por último, crearemos el archivo correspondiente a API ROUTER, este tendrá las rutas que conectaran al servidor y la respuesta de esa ruta. Cada función tendrá sus parámetros necesarios, la conversión de la respuesta del query y la respuesta http 200 o http 500 que regresará en la respuesta. Comencemos por las importaciones y las 2 primeras rutas, que corresponden a 2 FInd, uno para extraer todos los documentos de la colección y otro para traer los documentos por autor.

Primera parte de Api Router

Por otro lado, declaramos las rutas correspondientes a un Update, un Delete y un Create, para completar el CRUD que estamos creando.

Segunda Parte de Api Router

Ya, por último, solamente añadiremos esas funciones a la función de inicio que llamamos en el main, pasamos estas funciones creadas a nuestro servicio.

Tercera parte de Api Router
Código completo de api_router/mod.rs
  • Ejecución del servidor:

En el mismo path donde creamos el proyecto, donde al principio creamos el proyecto con cargo init, debemos teclear 2 comando mas para compilar y ejecutar el proyecto:

cargo build

Y posteriormente:

cargo run

Si todo se compilo y ejecuto de forma correcta, tendremos muestro servidor escuchando en “localhost:4000”.

  • Ejemplos en Postman:

A continuación, probaremos cada ruta para saber que datos toma de entrada y cuales arroja de salida.

/get-all

Esta ruta no toma ningún parámetro de entrada, ya que por defecto hace un find a la tabla que especifiquemos en nuestro API SERVICE. Nos regresará un arreglo de objetos, donde cada uno tendrá la estructura que especifiquemos, en nuestro caso, solo obtenemos id, titulo y autor.

/get-by/Gabriel García Márquez

En el caso de la ruta para buscar con base al autor, esta si que requiere un parámetro que es el mismo nombre del autor, hay una nota que hacer aquí, y es que este parámetro no es un Query Param, simplemente es la ruta con un “/”. La respuesta será un arreglo de objetos con la misma estructura que el ejemplo anterior.

/add

En el caso de la ruta para crear nuevos registros, tomara como información de entrada un Json RAW que pasara por el cuerpo de la petición, justo como se ve en la imagen, este Json solo tendrá el titulo y el autor. Para el caso de la respuesta, si todo salió correcto, nos regresara el nuevo Object Id generado en Mongo DB.

/update/<id>

El Update es la ruta que mas datos requiere, primero que nada, en el cuerpo de la petición debemos pasar en Json RAW como en el ejemplo anterior, pero además debemos poner el Object Id de Mongo del registro a modificar en el URL como en la búsqueda por autor. Para el caso de la respuesta, únicamente nos regresara la cantidad de registros modificados.

/delete

Por último, el caso de nuestro Delete, este recibe un Json RAW el cual usara para comparar, de ente es importante mas que nada el título. La respuesta será la cantidad de registros eliminados.

  • Concusión:

Con la finalización de esta práctica, podemos concluir que Rust resulta un lenguaje idóneo para tareas donde las cargas de ejecución se encuentran mas cercanas al procesador y así explotar todo el potencial de este lenguaje compilado, ejemplo de estas cargas o procesos, es el servidor REST API que acabamos de lograr, un servidor que con base en la arquitectura y estructura que elegimos, es fácilmente escalable para realizar acciones mas complejas y así seguir aprovechando este lenguaje de programación.

  • Mas información y Agradecimientos:

El repositorio completo de esta practica lo puedes encontrar aquí.

Por su atención… Muchas gracias.

--

--