Skip to main content
Lesson 4 of 12Beginner⏱ 15 mins

Your First Server & Route

Initialize an HTTP server and create a simple route.

25% of path

Learning Objectives

  • Initialize HttpServer
  • Bind to a port
  • Create a GET route

##Creating the Server

Actix Web requires a runtime to execute asynchronous tasks. We use the actix_web::main macro to set up this runtime (which is built on top of Tokio).

The HttpServer::new function takes a closure that returns an App instance. The App is where you register your routes.

#Creating a Route

A route is simply a function that handles an incoming request and returns a response. In Actix Web, we use the #[get("/")] macro or the web::get() method to define routes. Let's look at the interactive code inspector to understand how they fit together.

Interactive Code Inspector

Select any highlighted line of code below to deeply inspect its mechanics, understand why it exists, and what happens if you change it.

src/main.rs
1use actix_web::{web, App, HttpServer, HttpResponse, Responder};
2
3async fn hello() -> impl Responder {
4 HttpResponse::Ok().body("Hello, world!")
5}
6
7#[actix_web::main]
8async fn main() -> std::io::Result<()> {
9 HttpServer::new(|| {
10 App::new()
11 .route("/", web::get().to(hello))
12 })
13 .bind(("127.0.0.1", 8080))?
14 .run()
15 .await
16}
Line 3 - 5
async fn hello() -> impl Responder { ... }

What it does

Defines an asynchronous handler function.

Why use it?

To process requests without blocking the worker thread.

How it works underneath

Returns an object implementing Responder, which Actix turns into an HTTP response.

If you remove it

You wouldn't have any business logic for your endpoints.

"What If?" Experiment

Learning by doing. Modify the code to see what happens.

Goal

Change how many workers run.

Starting Code
HttpServer::new(|| App::new().service(index))
Suggested Modification

Add .workers(2) before .bind().

Expected Result

The server runs with two worker threads instead of one per CPU core.

Explanation: Each worker has its own App instance. More workers handle more concurrent connections until you run out of CPU.

Common Mistakes

Anticipate and avoid these frequent pitfalls when working with this concept.

!

Port already in use

Another program (or an earlier copy of your server) is still listening on that port.

Incorrect Pattern
Error: Os { code: 98, kind: AddrInUse, message: "Address already in use" }
Correct Pattern
.bind(("127.0.0.1", 8081))?

Explanation: Stop the other process or choose a different port. The ? on bind makes the program exit with the error instead of hanging.

!

Binding to 127.0.0.1 inside Docker

127.0.0.1 only accepts connections from inside the container itself.

Incorrect Pattern
.bind(("127.0.0.1", 8080))?
Correct Pattern
.bind(("0.0.0.0", 8080))?

Explanation: Use 0.0.0.0 in containers so the published port can reach your server, and keep 127.0.0.1 for local development.

Practical Exercise

Add a second route

Task

Extend the server with GET /health that returns the text "ok".

Requirements

  • Keep the existing GET / route
  • Add GET /health returning "ok"

Need a hint?

Try solving the exercise in your local environment first. Once you have a solution, or if you get completely stuck, reveal the answer.

Real-World Application

A /health endpoint like this is what load balancers and Kubernetes probe to decide whether your service is alive, so almost every production Actix service exposes one.

Key Takeaways

  • HttpServer::new takes a closure that builds an App for every worker.
  • bind() opens the port and run().await keeps the server alive.
  • Register routes with #[get] style macros and .service(), or with .route().
  • Visit http://127.0.0.1:8080 to see your server respond.

Finished reading?

Mark this lesson as done to track your progress and earn XP.