Serving Your Personal Tutor with a RESTful API Using Axum

Welcome to the next step in building a personal tutor service with Axum. In the previous lesson, we focused on the TutorController, which manages tutoring sessions and handles student queries by interacting with both the model and service layers.

Now, we will take a significant step forward by creating a RESTful API for our personal tutor service using Axum, a modern web framework. We'll start by setting up the main Axum application, then adapt the TutorController to integrate with Axum's state management and routing.

Understanding RESTful APIs and Axum

RESTful APIs are a way for different software systems to communicate over the internet. They provide a set of rules that allow programs to exchange data. Axum is a modern, ergonomic web framework for building APIs. It is designed to be type-safe, modular, and easy to use, leveraging async capabilities.

To get started with Axum, add it to your project by including it in your Cargo.toml dependencies:

Once you have added these dependencies, you can use Cargo to build and run your application:

Now, we can use Axum to connect the components we've already built, allowing students to interact with our personal tutor service through a web interface. This will enable seamless communication between students and our tutoring service.

Creating the Controller Instance

Next, we create an instance of our TutorController to handle the core operations of our personal tutor service. We use structs and shared state to manage controller instances:

We wrap the controller in an Arc<Mutex<...>> to allow safe, concurrent access from multiple request handlers in our async application. This controller will manage tutoring sessions and process student queries, serving as the bridge between our API endpoints and the underlying service layer.

Defining Request Models with Serde

We define request and response models using structs and derive macros from the serde crate for serialization and deserialization:

These structs specify the expected structure of incoming requests and outgoing responses. Axum will automatically parse and validate JSON payloads against these models.

Managing Student Sessions

Each student is identified by a unique student_id, which is provided in the request payload. The client is responsible for generating and storing this identifier (for example, using a UUID). The server uses this student_id to manage sessions and personalize tutoring interactions.

To generate a new student_id, we'll use the uuid crate:

This approach keeps the API stateless and allows for flexible session management. Note that this is a simple and effective method for basic applications, in more advanced systems however, you might use authentication tokens, cookies, or server-managed sessions to provide stronger security and a better user experience.

For now, let's focus on creating a simple API using Axum.

Creating the Index Route

Now that we've initialized our Axum application and created our controller instance, let's create the API endpoints that will allow students to interact with our personal tutor service. In Axum, routes are defined using handler functions:

This route handles GET requests to the root path (/). It simply returns a welcome message as plain text.

Creating New Tutoring Sessions

Next, let's define a route for creating new tutoring sessions:

This route handles POST requests to /api/create_session. It expects a JSON payload containing the student_id. The controller's create_session method returns either a success response or an error, which is handled using the Result type. The Extension extractor is used to share application state (such as the controller instance) across different request handlers in Axum.

The AppError type is used for error handling and ensures that errors are returned in a consistent JSON format, as shown in the custom exception handling section later in this lesson.

Handling Student Queries

Let's also define a route for sending queries to the tutor:

This route handles POST requests to /api/send_query. It expects a JSON payload with the student_id, session_id, and query. The controller's send_query method processes the query and returns either a success response or an error.

Custom Exception Handling

To ensure consistent error responses across our API, we define a custom error type and implement the IntoResponse trait for it:

This implementation ensures that all errors are converted to JSON responses with a consistent structure, making it easier for clients to handle errors.

Adapting the TutorController

In the previous unit, the TutorController was a simple struct that managed a test session and delegated to the TutorService. To integrate with Axum and support stateless API requests, we should make several key changes:

  • The controller should now accept student_id and session_id as explicit parameters, rather than relying on internal state or a test session map.
  • Error handling can be standardized using a custom AppError type, allowing for consistent API responses.
  • The controller methods should support calls from async request handlers, supporting concurrent access via Arc<Mutex<...>>.

Here is how the updated TutorController might look like:

How Students Interact with the Personal Tutor API

To interact with our personal tutor service, a student can follow these steps:

  1. Access the Index Route (/): The student begins by accessing the index route of the API. This step can be used to verify that the API is running and available. The server responds with a welcome message, ready for interaction.

  2. Create a Tutoring Session (/api/create_session): Before sending a query, the student needs to create a new tutoring session. This is done by sending a POST request to the /api/create_session route with their student_id. The server will respond with a unique session ID, which is essential for identifying the tutoring session in subsequent interactions.

  3. Send a Query (/api/send_query): With the tutoring session established, the student can now send academic questions to the personal tutor. This involves sending a POST request to the /api/send_query route, including the student_id, session_id, and the query content. The server processes the query using the DeepSeek model and responds with the tutor's explanation, allowing the student to continue the learning conversation.

By following these steps, a student can effectively receive personalized academic support from our personal tutor service, leveraging the RESTful API to manage tutoring sessions and exchange queries.

Running the Axum Server

Now that we've defined our routes and prepared our controller for integration with Axum, the final step is to set up the server to run our application. We use the tokio runtime and the main function to launch the server:

This code block does several important things:

  1. The controller is created and shared across handlers using Arc<Mutex<...>>.
  2. The Axum Router is configured with the necessary routes and layers.
  3. The server listens on port 3000 and prints a message to indicate it is running.

This setup ensures that your application can handle multiple concurrent requests efficiently while maintaining safe access to shared state, and provides a clear entry point for interacting with your tutor API!

Summary and Next Steps

In this lesson, we successfully built a RESTful API for our personal tutor service using Axum. We set up an Axum application, defined routes for tutoring operations, and explored how the TutorController integrates with Axum's state management and error handling. This lesson marks a significant milestone in our course, as it brings together all the components we've developed so far into a functional web application that can deliver personalized academic support at scale.

As you move on to the practice exercises, take the opportunity to experiment with the Axum API and reinforce the concepts covered in this lesson. This hands-on practice will prepare you for further development and exploration of additional Axum features and RESTful API concepts.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal