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 serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
rust
use std::f64::consts::PI; #[derive(Debug, Clone, Copy)] pub struct LatLng {
Building geographic bounding boxes in Rust
geospatial
value-types
option
Intermediate
7 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 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.