rust
74 lines · 9 steps
Building a GitHub proxy handler in Axum
An Axum handler fetches a GitHub repo through a shared HTTP client and maps upstream failures onto clean HTTP status codes.
Explained by
highlit
1use axum::{
2 extract::{Path, State},
3 http::StatusCode,
4 response::IntoResponse,
5 Json,
6};
7use serde::{Deserialize, Serialize};
8use std::time::Duration;
9
10#[derive(Clone)]
11pub struct AppState {
12 pub http: reqwest::Client,
13 pub github_token: String,
14}
15
16impl AppState {
17 pub fn new(github_token: String) -> Self {
18 let http = reqwest::Client::builder()
19 .user_agent("acme-gateway/1.0")
20 .timeout(Duration::from_secs(10))
21 .pool_max_idle_per_host(16)
22 .build()
23 .expect("failed to build reqwest client");
24
25 Self { http, github_token }
26 }
27}
28
29#[derive(Deserialize)]
30struct GithubRepo {
31 full_name: String,
32 stargazers_count: u64,
33 open_issues_count: u64,
34}
35
36#[derive(Serialize)]
37struct RepoSummary {
38 name: String,
39 stars: u64,
40 open_issues: u64,
41}
42
43pub async fn repo_summary(
44 State(state): State<AppState>,
45 Path((owner, repo)): Path<(String, String)>,
46) -> Result<Json<RepoSummary>, StatusCode> {
47 let url = format!("https://api.github.com/repos/{owner}/{repo}");
48
49 let resp = state
50 .http
51 .get(&url)
52 .bearer_auth(&state.github_token)
53 .header("Accept", "application/vnd.github+json")
54 .send()
55 .await
56 .map_err(|_| StatusCode::BAD_GATEWAY)?;
57
58 if resp.status() == reqwest::StatusCode::NOT_FOUND {
59 return Err(StatusCode::NOT_FOUND);
60 }
61
62 let repo: GithubRepo = resp
63 .error_for_status()
64 .map_err(|_| StatusCode::BAD_GATEWAY)?
65 .json()
66 .await
67 .map_err(|_| StatusCode::BAD_GATEWAY)?;
68
69 Ok(Json(RepoSummary {
70 name: repo.full_name,
71 stars: repo.stargazers_count,
72 open_issues: repo.open_issues_count,
73 }))
74}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Sharing a single reusable HTTP client through application state lets every request benefit from connection pooling and consistent timeouts.
- 2Deserializing only the fields you care about keeps your types decoupled from the full shape of an upstream API.
- 3Mapping every upstream error onto a deliberate StatusCode turns a fragile proxy into a predictable one.
Related explainers
rust
use axum::{extract::{Path, State}, http::StatusCode, Json}; use dashmap::DashMap; use serde::Serialize; use std::sync::Arc;
Request coalescing in an Axum handler
caching
concurrency
request-coalescing
Advanced
8 steps
java
public interface GitHubClient { @GetExchange("/users/{username}") GitHubUser getUser(@PathVariable String username);
Declarative HTTP clients in Spring
http-client
declarative-api
proxy
Intermediate
8 steps
python
from functools import wraps import asyncio from fastapi import APIRouter, FastAPI, Request
Per-route request timeouts in FastAPI
decorators
async
timeouts
Intermediate
6 steps
rust
use std::net::Ipv4Addr; use std::str::FromStr; #[derive(Debug, Clone, Copy)]
Parsing and matching IPv4 CIDR ranges in Rust
bitwise-operations
parsing
error-handling
Intermediate
8 steps
rust
use std::sync::OnceLock; static CRC_TABLE: OnceLock<[u32; 256]> = OnceLock::new();
Table-driven CRC32 with lazy init in Rust
checksums
lazy-initialization
lookup-tables
Intermediate
8 steps
rust
use axum::{ body::Body, extract::Path, http::{header, HeaderMap, HeaderValue, StatusCode},
HTTP range requests for video streaming in Axum
http-range-requests
streaming
async-io
Advanced
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-github-proxy-handler-in-axum-explained-rust-fd50/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.