Loading page…
Rust walkthroughs
Loading page…
axum::routing::get and axum::routing::get_service for route handlers?axum::routing::get accepts handler functions that use Axum's extractor system, automatically deserializing request components into function parameters. axum::routing::get_service accepts any type implementing tower::Service, providing lower-level control over request handling without the extractor convenience. Use get for typical Axum handlers where you want automatic parameter extraction, JSON parsing, and response serialization. Use get_service when integrating existing Tower services, implementing middleware-like behavior, or when you need direct access to the raw Request and Response types without extractor magic.
use axum::{
routing::get,
Router,
extract::{Path, Query, Json},
response::IntoResponse,
};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct FilterParams {
category: Option<String>,
limit: Option<usize>,
}
#[derive(Serialize)]
struct Item {
id: u32,
name: String,
}
async fn list_items(
Path(user_id): Path<u32>,
Query(params): Query<FilterParams>,
) -> impl IntoResponse {
let category = params.category.as_deref().unwrap_or("all");
let limit = params.limit.unwrap_or(10);
Json(vec![
Item { id: 1, name: format!("Item for user {} in {}", user_id, category) },
])
}
fn app() -> Router {
Router::new()
.route("/users/:user_id/items", get(list_items))
}get accepts handler functions with extractors as parameters. Axum automatically extracts Path, Query, and other components.
use axum::{
routing::get_service,
Router,
};
use tower::Service;
use std::task::{Context, Poll};
use http::{Request, Response};
use hyper::body::Body;
// A simple service that handles requests directly
struct MyService;
impl Service<Request<Body>> for MyService {
type Response = Response<Body>;
type Error = Box<dyn std::error::Error + Send + Sync>;
type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
// Direct access to raw Request - no extractors
let path = req.uri().path();
let response = Response::builder()
.status(200)
.body(Body::from(format!("Path: {}", path)))
.unwrap();
std::future::ready(Ok(response))
}
}
fn app() -> Router {
Router::new()
.route("/raw", get_service(MyService))
}get_service accepts any Service implementation. You work with raw Request and Response.
use axum::{
extract::{Extension, Path, Json, Query, State},
response::IntoResponse,
Router,
routing::get,
};
use serde::{Deserialize, Serialize};
#[derive(Clone)]
struct AppState {
db: String, // Simplified for example
}
#[derive(Deserialize)]
struct SearchParams {
q: String,
page: Option<u32>,
}
#[derive(Serialize)]
struct SearchResult {
query: String,
results: Vec<String>,
}
async fn search(
State(state): State<AppState>,
Extension(user_id): Extension<u32>,
Query(params): Query<SearchParams>,
) -> impl IntoResponse {
// All extractors are automatically resolved
// State is injected from the router
// Extension is set via middleware
// Query is parsed from URL parameters
Json(SearchResult {
query: params.q,
results: vec![format!("Result from DB: {}", state.db)],
})
}
fn app() -> Router {
Router::new()
.route("/search", get(search))
.with_state(AppState { db: "mydb".to_string() })
}get handlers can use multiple extractors. Axum handles extraction automatically.
use axum::{Router, routing::get_service};
use tower::Service;
use http::{Request, Response, Method};
use hyper::body::Body;
use std::task::{Context, Poll};
struct ApiHandler;
impl Service<Request<Body>> for ApiHandler {
type Response = Response<Body>;
type Error = Box<dyn std::error::Error + Send + Sync>;
type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
// Must manually extract everything from the request
let path = req.uri().path().to_string();
let method = req.method().clone();
let headers = req.headers().clone();
// Manual routing based on method/path
let response = match (method, path.as_str()) {
(Method::GET, "/api/status") => {
Response::builder()
.status(200)
.body(Body::from("{\"status\":\"ok\"}"))
.unwrap()
}
(Method::POST, "/api/data") => {
// Would need to manually read body here
Response::builder()
.status(201)
.body(Body::from("Created"))
.unwrap()
}
_ => {
Response::builder()
.status(404)
.body(Body::from("Not Found"))
.unwrap()
}
};
std::future::ready(Ok(response))
}
}
fn app() -> Router {
Router::new()
.route("/api/*path", get_service(ApiHandler))
}With get_service, you handle routing, parsing, and response building manually.
use axum::{Router, routing::get_service};
use tower::ServiceBuilder;
use tower_http::{services::ServeFile, ServiceBuilderExt};
use std::path::PathBuf;
fn app() -> Router {
// Serve static files using an existing Tower service
Router::new()
// ServeFile is a Tower service that handles file serving
.route("/static/index.html", get_service(ServeFile::new("public/index.html")))
.route("/static/style.css", get_service(ServeFile::new("public/style.css")))
}
// Another example: using tower-http services
use tower_http::services::ServeDir;
fn app_with_serve_dir() -> Router {
Router::new()
// ServeDir handles all requests under /assets
.nest_service("/assets", get_service(ServeDir::new("public/assets")))
}get_service integrates existing Tower services like ServeFile and ServeDir.
use axum::{Router, routing::get_service};
use tower::ServiceBuilder;
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
use http::{Request, Response};
use hyper::body::Body;
struct MyApiService;
impl tower::Service<Request<Body>> for MyApiService {
type Response = Response<Body>;
type Error = Box<dyn std::error::Error + Send + Sync>;
type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
let response = Response::builder()
.status(200)
.body(Body::from("API response"))
.unwrap();
std::future::ready(Ok(response))
}
}
fn app() -> Router {
// Wrap service with Tower middleware
let service = ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(CompressionLayer::new())
.service(MyApiService);
Router::new()
.route("/api", get_service(service))
}get_service works naturally with Tower middleware layers.
use axum::{
Router,
routing::{get, get_service},
extract::Json,
response::IntoResponse,
};
use tower_http::services::ServeDir;
// Regular handler using extractors
async fn api_handler() -> impl IntoResponse {
Json(serde_json::json!({ "status": "ok" }))
}
fn app() -> Router {
Router::new()
// Use get for API endpoints with extractors
.route("/api/status", get(api_handler))
// Use get_service for static file serving
.nest_service("/static", get_service(ServeDir::new("public/static")))
// Use get_service for existing services
.route("/metrics", get_service(prometheus_service()))
}
fn prometheus_service() -> impl tower::Service<http::Request<hyper::body::Body>, Response = http::Response<hyper::body::Body>, Error = std::convert::Infallible> {
// A service that returns Prometheus metrics
tower::service_fn(|_req: http::Request<hyper::body::Body>| async {
Ok::<_, std::convert::Infallible>(
http::Response::builder()
.status(200)
.body(hyper::body::Body::from("# HELP requests_total Total requests\nrequests_total 42\n"))
.unwrap()
)
})
}Mix get and get_service based on what each endpoint needs.
use axum::{Router, routing::get_service};
use tower::service_fn;
use http::{Request, Response};
use hyper::body::Body;
fn app() -> Router {
Router::new()
.route("/health", get_service(service_fn(|_req: Request<Body>| async {
// service_fn creates a Service from a closure
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(
Response::builder()
.status(200)
.body(Body::from("{\"healthy\":true}"))
.unwrap()
)
})))
}service_fn creates a service from an async closure, useful for simple get_service handlers.
use axum::{
routing::{get, get_service},
Router,
extract::Json,
response::{IntoResponse, Response},
};
use tower::Service;
use http::Request;
use hyper::body::Body;
use serde::Serialize;
#[derive(Serialize)]
struct ErrorResponse {
error: String,
}
// get handler: use Result with IntoResponse
async fn handler_with_error() -> Result<Json<String>, impl IntoResponse> {
// Axum's IntoResponse for Result handles errors
Err((http::StatusCode::BAD_REQUEST, "Invalid request"))
}
// get_service: handle errors in the Service impl
struct ErrorService;
impl Service<Request<Body>> for ErrorService {
type Response = Response<Body>;
type Error = Box<dyn std::error::Error + Send + Sync>;
type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn call(&mut self, _req: Request<Body>) -> Self::Future {
// Must build error response manually
let response = Response::builder()
.status(400)
.body(Body::from("{\"error\":\"Bad request\"}"))
.unwrap();
std::future::ready(Ok(response))
}
}get handlers use Rust's Result with automatic error conversion. get_service requires manual error handling.
use axum::{
routing::{get, get_service},
Router,
extract::State,
};
use tower::Service;
use http::{Request, Response};
use hyper::body::Body;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
data: String,
}
// get: State extractor provides easy access
async fn get_handler(State(state): State<AppState>) -> String {
state.data
}
// get_service: Must use request extensions or other mechanisms
struct ServiceWithState {
// Services need to hold state directly
state: AppState,
}
impl Service<Request<Body>> for ServiceWithState {
type Response = Response<Body>;
type Error = Box<dyn std::error::Error + Send + Sync>;
type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
// State is available through self
// Could also access through request extensions
let data = self.state.data.clone();
let response = Response::builder()
.status(200)
.body(Body::from(data))
.unwrap();
std::future::ready(Ok(response))
}
}
fn app() -> Router {
let state = AppState { data: "hello".to_string() };
Router::new()
.route("/extractor", get(get_handler))
.route("/service", get_service(ServiceWithState { state: state.clone() }))
.with_state(state)
}get handlers use State extractor. get_service requires state in the service itself.
use axum::{
routing::{get, get_service},
Router,
};
use tower::Service;
// get: Overhead from extractor resolution
async fn extractors_overhead() {
// Each extractor has resolution cost:
// - Path parsing
// - Query deserialization
// - JSON body parsing
// - Extension lookups
// These costs add up but are usually negligible
}
// get_service: Potentially lower overhead for simple handlers
struct MinimalService;
impl Service<http::Request<hyper::body::Body>> for MinimalService {
type Response = http::Response<hyper::body::Body>;
type Error = std::convert::Infallible;
type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn call(&mut self, _req: http::Request<hyper::body::Body>) -> Self::Future {
// Direct response without extractor overhead
std::future::ready(Ok(http::Response::new(hyper::body::Body::from("OK"))))
}
}
// In practice: use get for most handlers
// The convenience outweighs minimal overhead
// Use get_service when:
// - Integrating existing services
// - Need raw request access
// - Static file serving (tower-http services)Performance differences are usually negligible. Choose based on convenience and integration needs.
use axum::{
routing::{get, get_service},
Router,
extract::{Json, Path},
};
use tower_http::services::ServeDir;
use tower::service_fn;
// Use get for: handlers that need extractors
async fn api_handler(
Path(id): Path<u32>,
Json(payload): Json<serde_json::Value>,
) -> Json<serde_json::Value> {
Json(serde_json::json!({ "id": id, "received": payload }))
}
// Use get_service for: existing Tower services
fn setup_routes() -> Router {
Router::new()
// Handler with extractors -> get
.route("/api/:id", get(api_handler))
// Static file serving -> get_service
.nest_service("/static", get_service(ServeDir::new("public")))
// Health check with minimal overhead -> get_service
.route("/health", get_service(service_fn(|_| async {
Ok::<_, std::convert::Infallible>(
http::Response::new(hyper::body::Body::from("OK"))
)
})))
// Existing service -> get_service
.route("/metrics", get_service(prometheus_exporter()))
}Choose based on your needs: extractors favor get, integration favors get_service.
| Aspect | get | get_service |
|--------|-------|---------------|
| Handler type | Async function | Tower Service |
| Extractors | Automatic parameter extraction | Manual request parsing |
| State access | State extractor | Service struct fields or extensions |
| Error handling | Result with IntoResponse | Manual response building |
| Integration | Axum-specific | Any Tower service |
| Static files | Possible but verbose | Use ServeDir, ServeFile |
| Request access | Through extractors | Raw Request<Body> |
axum::routing::get and axum::routing::get_service serve different purposes in route handling:
get is for Axum-native handlers. It accepts async functions where parameters are automatically extracted using Axum's extractor system. Use get when:
State extractorget_service is for Tower service integration. It accepts any Service<Request> implementation, giving raw access to the HTTP request and response. Use get_service when:
ServeDir, ServeFile, etc.)Key insight: The choice isn't about capability—both can handle any HTTP request. The difference is ergonomics vs. control. get provides excellent ergonomics through extractors, making common patterns trivial. get_service provides low-level control for integrating with the broader Tower ecosystem. Most applications use get for route handlers and get_service for static files, metrics endpoints, or integrating pre-existing services.