This is the second article in our "Rust for NodeJS developers" series. If you missed the previous article, you can read it here: Rust for NodeJS developers (I) - Why and how?.
Rocket is a robust web framework for Rust, offering developers a streamlined approach to building high-performance web applications.
In this article, we'll dive into how to get started with Rocket.
Assuming Rust and Cargo are already set up, creating a new Rocket project is a breeze. Simply run the following command:
cargo new rust-for-nodejs-developers --bin
This command initializes a new Rust project named rust_for_nodejs_developers
with a binary executable.
Next, navigate into your project directory:
cd rust-for-nodejs-developers
Now, let's add Rocket as a dependency in your Cargo.toml
file.
Open the Cargo.toml
file in your project directory and add the following line under the [dependencies]
section:
rocket = { version = "0.5.1", features = ["json"] }
This line specifies that your project depends on the Rocket crate and specifies the version to use.
Be sure to check the Rocket website for the latest version.
With Rocket added as a dependency, you're all set to begin building your web application, let's create a src/main.rs
file with the following code:
use rocket::{
get, launch, routes,
serde::json::{json, Value},
};
#[get("/")]
fn get_root() -> &'static str {
"Hello, world!"
}
#[get("/users")]
fn get_users() -> Value {
json!([{ "name": "user" }])
}
#[get("/users/<user_id>/friends")]
fn get_user_friends(user_id: u8) -> Value {
json!([{ "friend_id": user_id }])
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![get_root, get_users, get_user_friends])
}
Now, simply execute cargo run
and you'll see output similar to:
🔧 Configured for debug.
>> address: 127.0.0.1
>> port: 8000
>> workers: 8
>> max blocking threads: 512
>> ident: Rocket
>> IP header: X-Real-IP
>> limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
>> temp dir: /tmp
>> http/2: true
>> keep-alive: 5s
>> tls: disabled
>> shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
>> log level: normal
>> cli colors: true
📬 Routes:
>> (get_root) GET /
>> (get_users) GET /users
>> (get_user_friends) GET /users/<user_id>/friends
📡 Fairings:
>> Shield (liftoff, response, singleton)
🛡️ Shield:
>> X-Content-Type-Options: nosniff
>> X-Frame-Options: SAMEORIGIN
>> Permissions-Policy: interest-cohort=()
🚀 Rocket has launched from http://127.0.0.1:8000
An essential tool for streamlining the development process is cargo-watch, akin to nodemon
, monitors for changes and automatically restart the server.
Install it with cargo install cargo-watch
.
Then, instead of cargo run
, utilize cargo watch -x run
to start the server in "watch" mode.
Now that we've configured our development environment, let's refine our code further.
Instead of cramming everything into a single file, we'll break it down into smaller and more modular components.
This approach facilitates maintenance and testing as our API grows in complexity.
We will start by refactoring from the deepest handler up to the root handler.
src/routes/users/user_friends/user_friends_routes.rs
use rocket::{
get, routes,
serde::json::{json, Value},
Route,
};
pub fn user_friends_routes() -> Vec<Route> {
routes![get_user_friends]
}
#[get("/<user_id>/friends")]
fn get_user_friends(user_id: u8) -> Value {
json!([{ "friend_id": user_id }])
}
We have moved the handler to a new file while maintaining a folder structure that closely mirrors the related URL. Note that parameters cannot be separated from the handlers that require them.
src/routes/users/user_friends/mod.rs
mod user_friends_routes;
pub use user_friends_routes::user_friends_routes;
Here, we expose our public function and flatten the use route by re-exporting each method, making it directly accessible from this module.
This allows us to use it as use user_friends::user_friends_routes
instead of use user_friends::user_friends_routes::user_friends_routes
in other modules.
src/routes/users/users_routes.rs
use rocket::{
get, routes,
serde::json::{json, Value},
Route,
};
use super::user_friends::user_friends_routes;
pub fn users_routes() -> Vec<Route> {
[routes![get_users], user_friends_routes()].concat()
}
#[get("/")]
fn get_users() -> Value {
json!([{ "name": "user" }])
}
In this file, we export both the users
and user_friends
routes since both paths begin with /users
.
src/routes/users/mod.rs
mod user_friends;
mod users_routes;
pub use users_routes::users_routes;
Here, we export and flatten our routes, and we also include the user_friends
module.
src/routes/root_routes.rs
use rocket::{get, routes, Route};
pub fn root_routes() -> Vec<Route> {
routes![get_root]
}
#[get("/")]
fn get_root() -> &'static str {
"Hello, world!"
}
src/routes/mod.rs
mod root_routes;
mod users;
pub use root_routes::root_routes;
This follows the same pattern as the other handlers.
src/routes/routes_fairing.rs
use rocket::{
fairing::{Fairing, Info, Kind, Result},
Build, Rocket,
};
use super::{root_routes, users::users_routes};
pub struct RoutesFairing {}
#[rocket::async_trait]
impl Fairing for RoutesFairing {
fn info(&self) -> Info {
Info {
name: "Routes module",
kind: Kind::Ignite,
}
}
async fn on_ignite(&self, rocket: Rocket<Build>) -> Result {
Ok(rocket
.mount("/", root_routes())
.mount("/users", users_routes()))
}
}
A fairing is Rocket's approach to structured middleware.
Here, we separate the logic required to mount each route. Each route node is responsible for mounting its child routes, cascading down the hierarchy.
src/routes/mod.rs
mod root_routes;
mod users;
mod routes_fairing;
pub use root_routes::root_routes;
pub use routes_fairing::RoutesFairing;
This adds the necessary lines to export and flatten the routes_fairing
components.
src/lib.rs
mod routes;
use rocket::{build, Build, Rocket};
use routes::RoutesFairing;
pub fn build_server() -> Rocket<Build> {
build().attach(RoutesFairing {})
}
With this, we isolate the server build logic in a library, making it easier to test.
src/main.rs
use rocket::launch;
use rust_for_nodejs_developers::build_server;
#[launch]
fn rocket() -> _ {
build_server()
}
Finally, our main function is clean and ready to run, utilizing the build_server
function we just defined.
You can check the result in this branch.
In the next installment, we'll take our project to the next level by setting up a development environment using Docker. Stay tuned!
Docker can be beneficial not just for deploying applications but also for local development. By creating a Docker environment for our Rust API, we can ensure a consistent and isolated development experience across different machines and team members.
Leer el artículoWe are always open to exploring new technologies. Recently, Rust has caught our attention due to its high performance, memory safety and reliability. In this series of articles, we will share the experience of learning Rust as a Node.js developer by building a GraphQL API in Rust.
Leer el artículoGoLang has the option to create shared libraries in C, and in this post I will show you how to do it.
Leer el artículo