Building RESTful APIs in Rust With Actix and Diesel

There are many packages and tools that you can use to facilitate your API development with Rust. Rust has a rich third-party ecosystem of crates for building APIs, including web packages like Actix and Rocket and ORMs like Diesel and SeaORM.

This article delves into using Actix and Diesel to build web applications. You’ll learn by building a CRUD API with persistence through Diesel on a Sqlite database.

Actix is a high-performance web framework that runs on the actor model. It is great at handling concurrent requests with lightweight actors that communicate asynchronously. Actix’s architecture promotes scalability and responsiveness, which is ideal for building performant web apps.

For data persistence, Diesel is a mature ORM that flexibly acts as a bridge between Rust data types and database tables. You’ll write native Rust programs, and Diesel will create statements and queries to execute on your preferred DBMS.

Setting up the API Development Environment

Setting up a development environment for building REST APIs in Rust is relatively simple. You must download and install a recent version of Rust on your computer to get started.

Run this command on your terminal to verify that you’ve successfully installed Rust and Cargo (Rust’s package management tool).

Next, Run these commands on your terminal to create and initialize a new Rust project on your computer:

The cargo init command initializes a new Rust project in the specified working directory. The command also creates a cargo.toml file in the directory for managing your project’s dependencies.

You’ll need some form of persistence for your database. In this tutorial, you’ll learn how to use Diesel and an SQL database for persistence for your API. Diesel supports a variety of SQL databases, including Sqlite, MySQL, and PostgreSQL. Install your preferred database management system, and you’re good to go.

Setting Up the Database for Persistence

You have to add the diesel and dotenv crates as project dependencies in the dependencies section of your cargo.toml file.

Once you’ve added these crates as dependencies, you must install the diesel_cli tool to interact with Diesel over the command line for migrations and schema generation.

Run this command to install the diesel_cli tool:

You can run the diesel_cli tool diesel_cli command after installing the tool.

Next, create an environment variables file and specify your database URL with the DATABASE_URL field. Run this command to create and insert the database URL for an in-memory Sqlite database.

Next, run the setup command for Diesel to set up a database for your project:

The setup command creates a migrations directory, the database specified in the DATABASE_URL, and runs existing migrations.

After you’ve set up your database with Diesel, you’ll use the migration generate command to generate SQL migration files. You’ll add the name of the migration as an argument to the migration generate command:

The command generates two SQL files in the migrations directory: up.sql and down.sql.

Write SQL for your database table definitions in the up.sql file as thus:

Then, write the SQL code to drop database tables in the down.sql file:

After writing the SQL files, run the migration run command to apply pending migrations.

After a successful process, you can use the print-schema command to print the schema. The command prints the contents of the schema.rs file.

diesel print-schema

The output of the print_schema command is Rust code that matches your SQL schema.

Attach the schema.rs file to your main.rs file with the mod schema directive to use the contents of the schema.rs file in the main.rs file and other parts of your package.

You must declare structs for data serialization, migrations, and deserialization operations. You can create a models.rs file and add struct definitions to match your database schema.

Here are the structs for the CRUD operations:

The request handler functions returns the Student struct. You can use the NewStudent for data migration and the UpdateStudent struct for PUT requests.

Connecting to Your SQL Database With Diesel

You’ll use the env and Connection to connect your SQL database with Diesel.

Here’s how you can connect to an SQLite database with a function and return a connection instance:

The establish_connection function returns the connection instance struct (SqliteConnection). The establish_connection loads the environment variables with the ok function accesses the database URL with the var function, and establishes a connection with the database via the URL with the establish function.

Setting Up Actix-web for Routing and Server Operations

After setting up the database for persistence, you can set up a server with Actix with routes for your handler functions.

Here are the contents of the [main.rs] file containing the main function that handles the routing and starts the server:

The main function is an asynchronous function that starts the server with the HttpServer::new function that takes in the App::new function, which handles the routes. The bind function binds the routes to the specified host, and the run function runs the server.

After setting up the server, you can import these functions in your [handlers.rs](<http://handlers.rs>) file, where you’ll specify the handler functions for the API.

Once you’ve imported the necessary functions and types, you can start writing the handler functions that run when users make API requests to the server.

The POST Request Handler Function

The create_human function is the POST request handler function. The create_human function will retrieve the JSON payload from the request and return a JSON payload containing a success message to the client.

The create_human handler function connects to the database with the establish_connection function, inserts the new_human data from the JSON payload inton the database with the insert_into function, and uses the HttpResponse::Ok function to return a JSON payload after a successful request.

Here’s a CURL request that you can use to test the create_human handler function.

Here’s the result of a successful data insertion operation on the database:

The GET Request Handler Function

The get_humans function is a GET request handler function that retrieves all the entries in the database and returns them to the client.

After connecting to the database, the get_humans handler function uses the load function to load all the Human entries in the database.

Here’s a CURL request that makes a call to trigger the functionality of the get_humans handler function:

On sending a GET request to the API via CURL you should expect to retrieve the entries in the database as such:

The PUT Request Handler Function

The update_human function is an asynchronous function that takes the id and a JSON payload from the client’s request and returns the data from the payload after updating the database entry.

The update_human handler function uses the update function to update the entry after confirming that the id exists with the find function.

Here’s a CURL request that sends a PUT request to the database and triggers the update_human function.

On sending the CURL request, here’s the result of the update operation:

The DELETE Request Handler Function

The DELETE request handler function will retrieve the id from the request and delete the row with the id from the database.

The delete_human handler function deletes the entry with the id from the database with Diesel’s delete function and returns a string response to confirm the operation’s success.

Here’s a CURL request that seeks to delete the entry having the id field of 1 from the database:

The result from a successful DELETE request to the database should have this form:

Conclusion

This guide explored setting up a RESTful API in Rust using Actix, Diesel, and SQLite. You have learned to define CRUD operations and used CURL commands to demonstrate how the API responds to different requests. Rust’s flexibility, combined with Actix’s speed and Diesel’s versatility, makes this stack a powerful choice for backend development.

As you progress with Rust, you’ll find many more libraries and tools in the Rust ecosystem that you can use to extend and improve your API. You can take the functionality of this API a step further by adding authentication and authorization mechanisms to secure your API and implement logging and error handling to make debugging easier.