rust
50 lines · 8 steps
Optimistic concurrency with ETags in Axum
An Axum handler uses If-Match and ETag headers to reject stale writes and prevent lost updates.
Explained by
highlit
1use axum::{
2 extract::{Path, State},
3 http::{header, HeaderMap, StatusCode},
4 response::{IntoResponse, Response},
5 Json,
6};
7use serde::Deserialize;
8use std::sync::Arc;
9
10#[derive(Deserialize)]
11pub struct UpdateArticle {
12 pub title: String,
13 pub body: String,
14}
15
16pub async fn update_article(
17 State(repo): State<Arc<ArticleRepo>>,
18 Path(id): Path<i64>,
19 headers: HeaderMap,
20 Json(payload): Json<UpdateArticle>,
21) -> Response {
22 let if_match = match headers.get(header::IF_MATCH) {
23 Some(value) => value.to_str().unwrap_or_default().trim_matches('"').to_owned(),
24 None => {
25 return (StatusCode::PRECONDITION_REQUIRED, "If-Match header required").into_response()
26 }
27 };
28
29 let current = match repo.find(id).await {
30 Ok(Some(article)) => article,
31 Ok(None) => return StatusCode::NOT_FOUND.into_response(),
32 Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
33 };
34
35 if current.version.to_string() != if_match {
36 return StatusCode::PRECONDITION_FAILED.into_response();
37 }
38
39 match repo
40 .update_if_version(id, current.version, &payload.title, &payload.body)
41 .await
42 {
43 Ok(Some(updated)) => {
44 let etag = format!("\"{}\"", updated.version);
45 ([(header::ETAG, etag)], Json(updated)).into_response()
46 }
47 Ok(None) => StatusCode::PRECONDITION_FAILED.into_response(),
48 Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
49 }
50}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Conditional requests via If-Match let you detect and reject writes based on stale data.
- 2Comparing a version token before and during the update guards against races between the check and the write.
- 3Returning the new ETag on success lets clients chain further conditional updates safely.
Related explainers
ruby
class Order class InvalidTransition < StandardError; end TRANSITIONS = {
A state machine for order transitions in Ruby
state-machine
data-driven
error-handling
Intermediate
8 steps
rust
use std::cmp::Ordering; pub struct PrefixIndex { entries: Vec<String>,
Prefix search with binary partitioning in Rust
binary-search
sorting
case-insensitive
Intermediate
7 steps
rust
use std::cmp::Ordering; use std::str::FromStr; #[derive(Debug, Clone, PartialEq, Eq)]
Parsing and ordering semantic versions in Rust
parsing
trait-implementation
ordering
Intermediate
8 steps
javascript
'use client'; import { useEffect } from 'react'; import * as Sentry from '@sentry/nextjs';
How a Next.js error boundary recovers
error-boundary
error-handling
observability
Intermediate
8 steps
ruby
class Registration < ApplicationRecord belongs_to :event validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
Validating registrations in Rails
validations
i18n
error-handling
Intermediate
8 steps
rust
use axum::{ extract::State, routing::{get, post, MethodRouter}, Json, Router,
Self-documenting routes in Axum
builder-pattern
closures
shared-state
Intermediate
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/optimistic-concurrency-with-etags-in-axum-explained-rust-7720/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.