Adding a new Endpoint
Instead of having our app always greet the entire world, let's make it so that visitors can specify their name to be personally greeted:
curl -H 'Content-Type: application/json' \
-d '{ "name": "Ferris"} ' \
-X POST \
http://127.0.0.1:3000/greet_me
Implementing the controller function
For now the above curl command will result in an error since our app only exposes the /greet
endpoint and nothing handles /greet_me
. Let's add that endpoint to web/src/controllers/greeting.rs
:
…
+#[axum::debug_handler]
+pub async fn hello_person(State(app_state): State<SharedAppState>) -> Json<Greeting> {
+ app_state.count_visit();
+ Json(Greeting {
+ hello: String::from("<user-name>"),
+ visit: app_state.get_visit_count(),
+ })
+}
…
We keep the counting of visits from the previous step. Now, instead of <user-name>
, we want to use the user's name that they send in the POST
data of course. In order to process the data, we need a struct that describes its shape:
// web/src/controllers/greeting.rs
…
+#[derive(Deserialize, Serialize)]
+ pub struct PersonalData {
+ pub name: String,
+ }
…
That struct can be consumed in the new hello_person
endpoint:
// web/src/controllers/greeting.rs
…
#[axum::debug_handler]
+pub async fn hello_person(
+ State(app_state): State<SharedAppState>,
+ Json(person): Json<PersonalData>,
+) -> Json<Greeting> {
-pub async fn hello_person(State(app_state): State<SharedAppState>) -> Json<Greeting> {
app_state.count_visit();
Json(Greeting {
+ hello: person.name,
- hello: String::from("<user-name>"),
visit: app_state.get_visit_count(),
})
}
Routing
The last step for making the endpoint work at /greet_me
is to route it. All of the application's routes are defined in web/src/routes.rs
so let's add it there:
use crate::controllers::greeting;
use crate::state::AppState;
+use axum::{routing::{get, post}, Router};
-use axum::{routing::get, Router};
use std::sync::Arc;
/// Initializes the application's routes.
///
/// This function maps paths (e.g. "/greet") and HTTP methods (e.g. "GET") to functions in [`crate::controllers`] as well as includes middlewares defined in [`crate::middlewares`] into the routing layer (see [`axum::Router`]).
pub fn init_routes(app_state: AppState) -> Router {
let shared_app_state = Arc::new(app_state);
Router::new()
.route("/greet", get(greeting::hello))
+ .route("/greet_me", post(greeting::hello_person))
.with_state(shared_app_state)
}
We can now invoke the new endpoint and enjoy our personal greeting 🦀:
» curl -H 'Content-Type: application/json' -d '{ "name": "Ferris"} ' -X POST http://127.0.0.1:3000/greet_me
{"hello":"Ferris","visit":1}%
Testing
Now that the endpoint is working as intended, let's add a test to make sure we catch any potential regressions that break it in the future. Since a test for the greeting
controller exists already in web/tests/api/greeting_test.rs
, we can add a new test case there:
+use axum::{
+ body::Body,
+ http::{self, Method},
+};
use googletest::prelude::*;
use my_app_macros::test;
+use my_app_web::controllers::greeting::{Greeting, PersonalData};
-use my_app_web::controllers::greeting::Greeting;
use my_app_web::test_helpers::{BodyExt, RouterExt, TestContext};
+use serde_json::json;
…
+#[test]
+async fn test_personal_greeting(context: &TestContext) {
+ let payload = json!(PersonalData {
+ name: String::from("Ferris"),
+ });
+ let response = context
+ .app
+ .request("/greet_me")
+ .method(Method::POST)
+ .body(Body::from(payload.to_string()))
+ .header(http::header::CONTENT_TYPE, "application/json")
+ .send()
+ .await;
+
+ let greeting: Greeting = response.into_body().into_json().await;
+ assert_that!(greeting.hello, eq(&String::from("Ferris")));
+}
The request can be built up step-by-step using the testing convenience functions that are available on the application the test receives via the test context.
Finally, in the last step of the tutorial, let's add a middleware.