<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Web on</title><link>/tags/web/</link><description>Recent content in Web on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Mon, 28 Oct 2024 08:55:00 +0000</lastBuildDate><atom:link href="/tags/web/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 12: Production Deployment — Docker, graceful shutdown, observability</title><link>/post/rust/rust-web-production/</link><pubDate>Mon, 28 Oct 2024 08:55:00 +0000</pubDate><guid>/post/rust/rust-web-production/</guid><description>&lt;p&gt;Shipping to production is where the real education begins. Your local dev environment is a controlled fantasy — one instance, no load balancer, fast database on localhost, unlimited memory. Production is a hostile environment where your service gets killed mid-request, runs out of memory at 3am, and needs to tell you what went wrong without you SSH-ing into a container. This lesson is about surviving out there.&lt;/p&gt;
&lt;h2 id="dockerfile-the-multi-stage-build"&gt;Dockerfile: The Multi-Stage Build&lt;/h2&gt;
&lt;p&gt;Rust binaries are statically linked (or nearly so). A compiled Rust service can run in a scratch or distroless container with no runtime dependencies. This means tiny images — often under 20MB.&lt;/p&gt;</description></item><item><title>Lesson 11: Integration Testing HTTP Services — Testing without mocks</title><link>/post/rust/rust-web-testing/</link><pubDate>Thu, 24 Oct 2024 15:40:00 +0000</pubDate><guid>/post/rust/rust-web-testing/</guid><description>&lt;p&gt;I worked on a codebase that had 600 unit tests with mocked HTTP clients, mocked databases, mocked everything. All 600 passed. The application didn&amp;rsquo;t work. The mocks were wrong — they returned data in a format the real database never produced. Those 600 tests gave the team confidence to ship broken code. Integration tests that hit real infrastructure are harder to write but they tell you whether your application actually works.&lt;/p&gt;</description></item><item><title>Lesson 10: OpenAPI / Swagger Generation — Documentation from code</title><link>/post/rust/rust-web-openapi/</link><pubDate>Mon, 21 Oct 2024 10:05:00 +0000</pubDate><guid>/post/rust/rust-web-openapi/</guid><description>&lt;p&gt;I&amp;rsquo;ve never seen a team maintain a separate OpenAPI spec in sync with their actual API for more than three months. Someone adds a field, forgets to update the docs, and suddenly the spec says one thing and the API does another. The only API documentation that stays accurate is documentation generated from the code itself. If the code changes, the docs change. No human discipline required.&lt;/p&gt;
&lt;h2 id="the-approach-utoipa"&gt;The Approach: utoipa&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;utoipa&lt;/code&gt; is the go-to crate for generating OpenAPI specs from Rust code. It uses derive macros and attribute annotations to produce an OpenAPI 3.1 JSON spec at compile time. You add annotations to your types and handlers, and utoipa generates a spec that&amp;rsquo;s always in sync with your code.&lt;/p&gt;</description></item><item><title>Lesson 9: Rate Limiting and Throttling — Protecting your service</title><link>/post/rust/rust-web-rate-limiting/</link><pubDate>Fri, 18 Oct 2024 13:10:00 +0000</pubDate><guid>/post/rust/rust-web-rate-limiting/</guid><description>&lt;p&gt;We launched a public API without rate limiting. Within a week, a single user was making 200 requests per second — not maliciously, just a badly written script with no backoff. Their traffic consumed 40% of our database connections and degraded performance for everyone else. We added rate limiting, their requests started getting 429s, they fixed their script, and everyone was happy. Should&amp;rsquo;ve been there from day one.&lt;/p&gt;
&lt;h2 id="why-rate-limit"&gt;Why Rate Limit&lt;/h2&gt;
&lt;p&gt;Three reasons, in order of importance:&lt;/p&gt;</description></item><item><title>Lesson 8: WebSockets with Axum — Real-time in Rust</title><link>/post/rust/rust-web-websockets/</link><pubDate>Wed, 16 Oct 2024 09:20:00 +0000</pubDate><guid>/post/rust/rust-web-websockets/</guid><description>&lt;p&gt;A startup I consulted for was polling their REST API every 500 milliseconds to check for new messages. Forty thousand clients, each making two requests per second. That&amp;rsquo;s 80,000 requests per second to check if anything changed — and 99% of the time, nothing had. They switched to WebSockets, dropped their server count from 12 to 2, and their AWS bill fell by 70%. Polling is fine for dashboards that refresh every 30 seconds. For anything real-time, you want WebSockets.&lt;/p&gt;</description></item><item><title>Lesson 7: Pagination, Filtering, and Sorting — API patterns that scale</title><link>/post/rust/rust-web-pagination/</link><pubDate>Mon, 14 Oct 2024 16:30:00 +0000</pubDate><guid>/post/rust/rust-web-pagination/</guid><description>&lt;p&gt;We shipped a &amp;ldquo;list all orders&amp;rdquo; endpoint that returned everything. No pagination. Worked great in development with 50 test records. In production, one customer had 340,000 orders. The endpoint took 12 seconds, the response was 45MB, and the frontend crashed trying to render it. We added pagination that afternoon. You should add it before that afternoon.&lt;/p&gt;
&lt;h2 id="offset-based-pagination"&gt;Offset-Based Pagination&lt;/h2&gt;
&lt;p&gt;The most common approach. Simple to implement, easy to understand, and good enough for most internal tools and admin panels.&lt;/p&gt;</description></item><item><title>Lesson 6: Database Integration — SQLx and connection management</title><link>/post/rust/rust-web-database/</link><pubDate>Sat, 12 Oct 2024 07:45:00 +0000</pubDate><guid>/post/rust/rust-web-database/</guid><description>&lt;p&gt;My first Rust web service leaked database connections. I opened a new connection per request and forgot that Rust&amp;rsquo;s ownership system doesn&amp;rsquo;t magically manage TCP sockets. After about 200 concurrent users, PostgreSQL refused new connections and the whole service went down. Connection pooling isn&amp;rsquo;t optional — it&amp;rsquo;s the first thing you set up.&lt;/p&gt;
&lt;h2 id="why-sqlx"&gt;Why SQLx&lt;/h2&gt;
&lt;p&gt;There are three main approaches to database access in Rust:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Diesel&lt;/strong&gt; — A full ORM with a query builder. Generates SQL at compile time. Requires a build step that connects to your database. Strong opinions about schema management.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SeaORM&lt;/strong&gt; — An async ORM inspired by ActiveRecord. Higher level, more magic. Good if you like ORMs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLx&lt;/strong&gt; — Not an ORM. You write SQL. SQLx compiles your SQL queries against a real database at compile time, verifying that your SQL is valid and your result types match the columns returned.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I use SQLx because I like writing SQL and I don&amp;rsquo;t trust ORMs in production. ORMs generate queries you can&amp;rsquo;t see, and when they generate bad queries (and they will), debugging is miserable. With SQLx, the SQL is right there in your code, and the compiler verifies it&amp;rsquo;s correct.&lt;/p&gt;</description></item><item><title>Lesson 5: Authentication — JWT, sessions, OAuth</title><link>/post/rust/rust-web-auth/</link><pubDate>Wed, 09 Oct 2024 11:00:00 +0000</pubDate><guid>/post/rust/rust-web-auth/</guid><description>&lt;p&gt;I&amp;rsquo;ve reviewed auth implementations at four different companies. Three of them stored passwords in SHA-256 without salting. One stored them in &lt;em&gt;plain text&lt;/em&gt; in a column called &lt;code&gt;password_encrypted&lt;/code&gt; — because naming it &amp;ldquo;encrypted&amp;rdquo; apparently counted as security. Auth is the part of your application that bad actors actively try to break. Getting it wrong isn&amp;rsquo;t a bug, it&amp;rsquo;s a liability.&lt;/p&gt;
&lt;h2 id="password-hashing-do-this-right-or-dont-do-it-at-all"&gt;Password Hashing: Do This Right or Don&amp;rsquo;t Do It At All&lt;/h2&gt;
&lt;p&gt;Before we talk about tokens or sessions, let&amp;rsquo;s nail password storage. The rules are simple and non-negotiable:&lt;/p&gt;</description></item><item><title>Lesson 4: Request Validation and Error Responses — Clean input handling</title><link>/post/rust/rust-web-request-validation/</link><pubDate>Mon, 07 Oct 2024 19:15:00 +0000</pubDate><guid>/post/rust/rust-web-request-validation/</guid><description>&lt;p&gt;A junior engineer on my team once deployed an endpoint that accepted any string as an email address. Someone submitted &amp;ldquo;lol&amp;rdquo; as their email, the downstream email service threw a cryptic error, and our error tracking lit up with 500s for an hour. Input validation isn&amp;rsquo;t glamorous, but skipping it is how you get paged at dinner.&lt;/p&gt;
&lt;h2 id="the-problem-with-default-error-responses"&gt;The Problem with Default Error Responses&lt;/h2&gt;
&lt;p&gt;Out of the box, Axum&amp;rsquo;s error responses are&amp;hellip; not great. Send malformed JSON to a &lt;code&gt;Json&amp;lt;T&amp;gt;&lt;/code&gt; handler and you get back:&lt;/p&gt;</description></item><item><title>Lesson 3: Middleware with Tower Layers — The composable middleware pattern</title><link>/post/rust/rust-web-middleware/</link><pubDate>Sat, 05 Oct 2024 08:30:00 +0000</pubDate><guid>/post/rust/rust-web-middleware/</guid><description>&lt;p&gt;I once inherited a Node.js codebase with 23 Express middleware functions chained together. Half of them silently swallowed errors, three of them conflicted with each other, and nobody knew what order they ran in. When I started building services in Axum, the Tower middleware model felt like a revelation — not because it&amp;rsquo;s easier (it&amp;rsquo;s actually harder at first), but because it makes middleware &lt;em&gt;composable&lt;/em&gt; and &lt;em&gt;type-checked&lt;/em&gt;. You can&amp;rsquo;t silently swallow errors when the type system forces you to handle them.&lt;/p&gt;</description></item><item><title>Lesson 2: Axum from Zero — Routing, handlers, extractors</title><link>/post/rust/rust-web-axum-intro/</link><pubDate>Thu, 03 Oct 2024 14:45:00 +0000</pubDate><guid>/post/rust/rust-web-axum-intro/</guid><description>&lt;p&gt;The first time I tried Axum, I wrote a handler that took five extractor arguments and spent twenty minutes staring at a compiler error that said my function &amp;ldquo;didn&amp;rsquo;t implement Handler.&amp;rdquo; Turns out the order of extractors matters, and there&amp;rsquo;s a limit on how many you can have. Nobody tells you that upfront. So I&amp;rsquo;m telling you now.&lt;/p&gt;
&lt;h2 id="routing-fundamentals"&gt;Routing Fundamentals&lt;/h2&gt;
&lt;p&gt;Axum&amp;rsquo;s router is just a struct that maps HTTP methods and paths to handler functions. No macros, no attributes — you build routes with method calls.&lt;/p&gt;</description></item><item><title>Lesson 1: The Rust Web Landscape — Axum, Actix, Rocket and why I pick Axum</title><link>/post/rust/rust-web-landscape/</link><pubDate>Tue, 01 Oct 2024 10:22:00 +0000</pubDate><guid>/post/rust/rust-web-landscape/</guid><description>&lt;p&gt;I spent three weeks building a service in Actix-web before ripping it out and switching to Axum. Not because Actix was bad — it&amp;rsquo;s genuinely fast and battle-tested. I switched because every time I needed custom middleware, I was fighting the framework instead of writing my application. That experience taught me something: in Rust web development, the framework you pick determines how much you fight the type system versus how much you work &lt;em&gt;with&lt;/em&gt; it.&lt;/p&gt;</description></item></channel></rss>