<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SATYA SOOTAR</title>
    <description>The latest articles on DEV Community by SATYA SOOTAR (@satyasootar).</description>
    <link>https://dev.to/satyasootar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3682982%2F06c7f09f-b260-4b90-9d89-dedbc07fd2d3.png</url>
      <title>DEV Community: SATYA SOOTAR</title>
      <link>https://dev.to/satyasootar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/satyasootar"/>
    <language>en</language>
    <item>
      <title>How Instagram, WhatsApp, Uber &amp; Netflix Would Be Built Today Using Expo Router</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Mon, 01 Jun 2026 16:57:52 +0000</pubDate>
      <link>https://dev.to/satyasootar/how-instagram-whatsapp-uber-netflix-would-be-built-today-using-expo-router-55c9</link>
      <guid>https://dev.to/satyasootar/how-instagram-whatsapp-uber-netflix-would-be-built-today-using-expo-router-55c9</guid>
      <description>&lt;p&gt;When we think about apps like Instagram, WhatsApp, Uber, or Netflix, it's easy to get lost in the glamour of their features. The smooth feed, the instant message delivery, the live driver tracking, the seamless streaming. But what truly separates these giants from the millions of apps that never make it off the ground isn't just a clever idea, it's the architecture that holds it all up.&lt;/p&gt;

&lt;p&gt;This isn't a blod on how to clone their UI pixel by pixel. You can find plenty of those. Instead, we're going to put on our engineering hats and explore how you would think about building the &lt;em&gt;core systems&lt;/em&gt; of these apps from the ground up, using modern tools like &lt;strong&gt;Expo Router&lt;/strong&gt;. The goal is to shift your mindset from "getting it to work" to "engineering for the long haul" and to show you how the right architecture makes all the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Myth of the Simple Folder Structure
&lt;/h2&gt;

&lt;p&gt;We've all been there. You start a new project, a brilliant idea in your head, and the folder structure looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── components/
├── screens/
├── hooks/
├── utils/
└── types/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feels clean and organized. For a small app with five screens, it works perfectly. But then, a year later, your startup has taken off, the team has grown from 1 to 15 engineers, and the app now has 50 screens.&lt;/p&gt;

&lt;p&gt;This simple folder structure becomes a nightmare. Opening the &lt;code&gt;components/&lt;/code&gt; folder is like looking into a junk drawer. You see &lt;code&gt;Button.tsx&lt;/code&gt;, &lt;code&gt;FeedPost.tsx&lt;/code&gt;, &lt;code&gt;ChatBubble.tsx&lt;/code&gt;, &lt;code&gt;RideCard.tsx&lt;/code&gt;, and no one knows which components are used where. Deleting a feature becomes a game of "hunt the import" across the entire project. It slows everyone down, and worse, it makes refactoring terrifying.&lt;/p&gt;

&lt;p&gt;For a small app, this structure is fine. For a production-scale app that demands high performance, maintainability, and team collaboration, it's an absolute disaster. The simple way fails because it's fundamentally based on the "type" of a file, not the "function" of a feature. This is the first, and perhaps most important, lesson of scaling your React Native application.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Way: The Feature-Based Architecture
&lt;/h2&gt;

&lt;p&gt;The production way of thinking is to organize your code by &lt;strong&gt;features&lt;/strong&gt;, not by file types. It's called "feature-based separation" or "colocation". The idea is simple: all the code that makes a single feature work lives in its own cozy folder.&lt;/p&gt;

&lt;p&gt;Let's visualize the difference. Imagine an app with a feed and a user profile.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjab6ou2cg3mj9a4wukt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjab6ou2cg3mj9a4wukt.png" alt="Small App vs. Production App Architecture" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the production structure, if you want to delete the "feed" feature, you delete the &lt;code&gt;/feed&lt;/code&gt; folder. Done. No hunting around. It's a massive cognitive win for developers, especially when a team is working on different features simultaneously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expo Router: The Architect's Best Friend
&lt;/h2&gt;

&lt;p&gt;This is where Expo Router shines. At its core, Expo Router is a file-based routing library built on top of React Navigation. This means the file structure of your &lt;code&gt;app&lt;/code&gt; folder directly defines your app's navigation. It's incredibly powerful for large applications because it enforces a clear, predictable navigation pattern that scales without any additional mental overhead.&lt;/p&gt;

&lt;p&gt;Let's see how we can marry feature-based architecture with Expo Router's file system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  _layout.tsx            // Root layout (check auth, setup providers)
  (auth)/               // Auth route group, no tab bar
    _layout.tsx
    login.tsx
    sign-up.tsx
  (app)/                // Protected app route group with tabs
    _layout.tsx         // Tabs layout (Feed, Search, Activity, Profile)
    (feed)/
      _layout.tsx       // Stack navigator for Feed
      index.tsx
      [postId].tsx
    (messages)/
      _layout.tsx       // Stack for Chat
      index.tsx
      [chatId].tsx
    profile/
      _layout.tsx
      index.tsx
      settings.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure is the foundation. It tells us instantly what's public (&lt;code&gt;auth&lt;/code&gt;), what's protected (&lt;code&gt;app&lt;/code&gt;), and how the user moves through the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Core Systems: A Four-Part Deep Dive
&lt;/h2&gt;

&lt;p&gt;Now, let's use this architecture to think about the core systems for our four apps. We're not building a clone, but rather, understanding the architectural decisions behind their core features.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Instagram: The Infinite Feed
&lt;/h3&gt;

&lt;p&gt;Instagram's main challenge is handling an endless stream of media-rich content. The core technical requirement is a high-performance, lazy-loading infinite scroll.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expo Router Structure:&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;(feed)&lt;/code&gt; folder would contain the main feed screen (&lt;code&gt;index.tsx&lt;/code&gt;) and the dynamic route for a single post (&lt;code&gt;[postId].tsx&lt;/code&gt;). This allows for deep linking directly to any post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State Management:&lt;/strong&gt; A combination of Zustand for global UI state (like the current user's feed preferences) and &lt;strong&gt;TanStack Query&lt;/strong&gt; (formerly React Query) for managing server-state is a modern, powerful choice. TanStack Query is a master at caching, background refetching, and pagination, which is exactly what a feed needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture in Action:&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;Feed&lt;/code&gt; screen would use a &lt;code&gt;FlatList&lt;/code&gt; with &lt;code&gt;getItemLayout&lt;/code&gt; for memory efficiency. As the user scrolls, TanStack Query's &lt;code&gt;useInfiniteQuery&lt;/code&gt; hook is triggered, fetching the next page of posts from an API. The API returns the data, which is then cached, and the UI updates instantly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev24rqn740efeh0gxun7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev24rqn740efeh0gxun7.png" alt="Feed rendering and infinite scroll flow" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Production Tradeoff:&lt;/strong&gt; To keep the app fast and prevent it from consuming too much memory, you implement virtualized lists and heavy pagination. The tradeoff is complexity in data fetching and state synchronization, but it's the only way to handle a feed that could literally go on forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. WhatsApp: Real-time Messaging with Offline-First Support
&lt;/h3&gt;

&lt;p&gt;WhatsApp is a masterclass in real-time communication and offline resilience. You type a message to a friend in a different country, and they see the little typing indicator before the message pops into their chat, instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expo Router Structure:&lt;/strong&gt;&lt;br&gt;
The chat interface would live in the &lt;code&gt;(messages)/[chatId].tsx&lt;/code&gt; screen. The deep linking with &lt;code&gt;[chatId]&lt;/code&gt; is crucial. When a user taps a push notification for a new message, Expo Router can automatically navigate them directly to that specific chat using its built-in universal linking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Technologies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;WebSockets (e.g., Socket.IO):&lt;/strong&gt; For the real-time, persistent connection to send and receive messages instantly.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Offline-First Database (e.g., SQLite, WatermelonDB):&lt;/strong&gt; All messages are first saved to a local database on the user's phone before they're even sent to the server.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Optimistic Updates:&lt;/strong&gt; The UI updates instantly. Your message appears in the chat bubble before the network request is complete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architecture in Action:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;You Send a Message:&lt;/strong&gt; The UI calls a function. The message is saved to the local SQLite database and given a temporary &lt;code&gt;id&lt;/code&gt;. The UI instantly renders the new message with a "sending" status icon.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Background Sync:&lt;/strong&gt; A background job picks up the unsent message from the queue. It sends it to the server via the WebSocket connection.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Server Confirms:&lt;/strong&gt; The server receives the message, broadcasts it to the recipient, and sends a confirmation back to your device.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Update Local State:&lt;/strong&gt; Your app receives the confirmation, updates the local database, and changes the message status from "sending" to "sent".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkx9a7zsu3rtvpv31hjzg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkx9a7zsu3rtvpv31hjzg.png" alt="Offline first message sync flow diagram" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Production Tradeoff:&lt;/strong&gt; The complexity here is high. You have to manage optimistic UI updates, conflict resolution (what if the message fails to send?), and keep your local database perfectly in sync with the server's version. The benefit, a blazing fast and reliable messaging experience that works on a shaky train connection, is worth every bit of the effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Uber: Maps and Live Location Tracking
&lt;/h3&gt;

&lt;p&gt;Uber's magic is the live map. You see a car moving in real-time, and its ETA updating by the second. This is all about managing a constant stream of location data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expo Router Structure:&lt;/strong&gt;&lt;br&gt;
The ride-tracking screen is a huge part of the app's flow. The &lt;code&gt;(app)&lt;/code&gt; group for a logged-in user would contain a &lt;code&gt;ride&lt;/code&gt; folder with dynamic routes for an active ride, like &lt;code&gt;ride/[rideId].tsx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Technologies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Maps SDK (e.g., MapView, Google Maps API):&lt;/strong&gt; For rendering the map and driver's location.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;WebSocket for Location Streaming:&lt;/strong&gt; The driver's phone is constantly publishing its coordinates to a server. The server then broadcasts this to the rider's app via a WebSocket connection.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Efficient Re-rendering:&lt;/strong&gt; Every new coordinate cannot cause a full screen re-render. You need to update only the map's marker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architecture in Action:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Ride Starts:&lt;/strong&gt; The backend creates a unique room for the ride.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Driver Publishes Location:&lt;/strong&gt; Every few seconds, the driver's app sends a "location update" message to the server's room.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Rider Listens:&lt;/strong&gt; The rider's app is subscribed to the same room via a WebSocket.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Map Updates:&lt;/strong&gt; When a new location arrives, the app uses a reference to the map's marker to smoothly animate it to the new position, without redrawing the whole screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53nfjkvp39rrxh3g4ekm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53nfjkvp39rrxh3g4ekm.png" alt="Real-time Driver Tracking" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Production Tradeoff:&lt;/strong&gt; The biggest challenge is handling spotty connections. What happens when the driver enters a tunnel? The app needs a robust reconnection strategy with exponential backoff and smart UI states that don't make the rider think the driver has vanished into thin air. This adds significant complexity to the WebSocket management.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Netflix: Heavy Content Delivery
&lt;/h3&gt;

&lt;p&gt;Netflix's challenge is getting you from a giant list of movies to watching one, all with zero buffering. It's a masterclass in loading states, smart pre-fetching, and efficient video playback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expo Router Structure:&lt;/strong&gt;&lt;br&gt;
The sheer number of screens is a perfect use case for Expo Router's dynamic routes. You'd have a &lt;code&gt;movie/[movieId].tsx&lt;/code&gt; route that can deep-link to any piece of content in Netflix's massive library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Technologies:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;FlatList with Horizontal Scrolling:&lt;/strong&gt; For rendering rows of movie posters, optimized for performance.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Thumbnail Pre-fetching:&lt;/strong&gt; As the user scrolls, the app starts downloading the images for the &lt;em&gt;next&lt;/em&gt; set of movies before they're even on screen.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Lazy Loading:&lt;/strong&gt; The video player and its heavy dependencies are only loaded when the user navigates to the movie screen.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Offline Downloads:&lt;/strong&gt; Users can download content directly to the device's file system for later viewing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architecture in Action:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Browse:&lt;/strong&gt; The home screen uses &lt;code&gt;FlatList&lt;/code&gt; to render rows. Each row is a horizontal list of movie thumbnails.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Prefetch:&lt;/strong&gt; As the user stops scrolling, the app examines what's about to come into view and sends a low-priority request to download those images.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Select a Movie:&lt;/strong&gt; The user taps on "Stranger Things".&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Lazy Load Details Screen:&lt;/strong&gt; The app quickly navigates to the &lt;code&gt;movie/[movieId].tsx&lt;/code&gt; screen. This screen's code bundle is small and loads fast.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Defer Loading:&lt;/strong&gt; Immediately, the screen shows a skeleton UI and starts fetching movie details. The actual video player component, which is huge, is only imported and rendered after the details have loaded, keeping the initial navigation snappy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyahzdsoflw0lrp7qd4p4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyahzdsoflw0lrp7qd4p4.png" alt="App Startup Optimization Lifecycle" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Production Tradeoff:&lt;/strong&gt; You trade a super simple "load everything at once" model for a much more complex "load on demand" system. Managing the state of what is loading, what is pre-fetched, and what is cached adds layers of complexity, but it's the only way to ensure your app doesn't feel slow and bloated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together: The Production-Grade Starter
&lt;/h2&gt;

&lt;p&gt;So, what does all of this look like in a single, coherent architecture? Here's a diagram of a production-grade folder structure using the principles we've discussed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F95msru1tmrw4m8g0oix1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F95msru1tmrw4m8g0oix1.png" alt="Production-Grade Expo Router Folder Structure" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This structure is your roadmap. It's how you build an app that doesn't just work for a few months, but for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigating the Waters: Authentication Flows and Shared Layouts
&lt;/h2&gt;

&lt;p&gt;Two of the most common challenges in any app are managing the authentication flow and creating nested, shared layouts. Expo Router handles both beautifully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication Flow:&lt;/strong&gt; With route groups, your &lt;code&gt;(auth)&lt;/code&gt; and &lt;code&gt;(app)&lt;/code&gt; folders are completely separate. Inside the root &lt;code&gt;_layout.tsx&lt;/code&gt;, you would check an authentication state (from your Zustand store or a secure token). If the user is logged out, it renders the &lt;code&gt;(auth)&lt;/code&gt; group (login, sign-up). If they're logged in, it renders the &lt;code&gt;(app)&lt;/code&gt; group (the main tabs). Expo Router v5 introduced &lt;code&gt;Stack.Protected&lt;/code&gt; which makes this pattern even more declarative and secure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared Layouts and Nested Routing:&lt;/strong&gt; You can easily create a stack navigator inside a tab. For instance, your feed screen can be a stack. The main &lt;code&gt;(app)/_layout.tsx&lt;/code&gt; creates the tabs. Then, inside the &lt;code&gt;(feed)/_layout.tsx&lt;/code&gt;, you create a stack navigator. This allows you to push a post details screen (&lt;code&gt;[postId].tsx&lt;/code&gt;) on top of the feed, while the bottom tab bar remains visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unseen Hero: The API and Networking Layer
&lt;/h2&gt;

&lt;p&gt;All the data for your app comes from an API. A clean networking layer is crucial. You should never directly call &lt;code&gt;fetch&lt;/code&gt; from within a screen component. Instead, you create a service layer.&lt;/p&gt;

&lt;p&gt;You would have a &lt;code&gt;services/api.ts&lt;/code&gt; file that configures a single instance of an HTTP client (like Axios). It sets up base URLs, authentication headers, and response interceptors for error handling. Then, for each feature, you create a feature-specific API file (e.g., &lt;code&gt;feed/api.ts&lt;/code&gt;) that calls your base client and exposes simple functions like &lt;code&gt;fetchFeed()&lt;/code&gt;. Your UI components only call these simple functions, keeping them blissfully unaware of the underlying network implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up: It's About Engineering Mindset
&lt;/h2&gt;

&lt;p&gt;Building an app at the scale of Instagram, WhatsApp, Uber, or Netflix isn't about a single magic library or a clever piece of code. It's a million small, deliberate decisions about architecture, tradeoffs, and developer experience.&lt;/p&gt;

&lt;p&gt;The tools we have today, especially Expo Router, provide an incredible foundation. With its file-based routing, deep linking, and universal design, it forces you into a structure that scales naturally. But a tool is only as good as the person wielding it.&lt;/p&gt;

&lt;p&gt;The next time you open a new React Native project, resist the temptation of the simple folder structure. Take a moment to think about the future. Map out your features. Group your routes. Ask yourself, "How would this code look to a new developer a year from now?".&lt;/p&gt;

&lt;p&gt;By asking these questions and following the principles of feature-based architecture, you're not just building an app. You're engineering a system built to last.&lt;/p&gt;




&lt;p&gt;Hope you liked this blog. If there’s any mistake or something I can improve, do tell me. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, I post more stuff there.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>design</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Expo Router vs React Navigation: Which One Should You Use in 2026?</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Mon, 01 Jun 2026 15:44:48 +0000</pubDate>
      <link>https://dev.to/satyasootar/expo-router-vs-react-navigation-which-one-should-you-use-in-2026-40mm</link>
      <guid>https://dev.to/satyasootar/expo-router-vs-react-navigation-which-one-should-you-use-in-2026-40mm</guid>
      <description>&lt;p&gt;If you've spent any time building React Native apps, you've already bumped into the question. You're setting up a new project, you need to move users between screens, and suddenly you're 40 minutes deep in a documentation rabbit hole wondering whether to go with the familiar React Navigation setup or take the leap to Expo Router. This article is going to break it all down, no hype, no fanboy energy, just an honest look at both options so you can make the call that actually fits your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, What Does "Routing" Even Mean in a Mobile App?
&lt;/h2&gt;

&lt;p&gt;On the web, routing is pretty intuitive. You type a URL, the browser goes to a page. Simple. In mobile apps, there's no URL bar, no browser history button, nothing like that. But users still need to move between screens, go back to where they came from, open modals, tab between sections, and all of that has to feel smooth and natural.&lt;/p&gt;

&lt;p&gt;So in mobile development, routing is basically the system you use to manage how screens stack on top of each other, how state is preserved when you go back, how deep links work, and how the app knows which screen to show based on where the user is in the flow. Think of it like this: routing is "moving between screens while keeping your app's memory intact." When someone logs in, fills out a form, gets distracted and opens a modal, then comes back, the app should remember all of that. Routing is the machinery underneath that makes it happen.&lt;/p&gt;

&lt;p&gt;In React Native, this doesn't come out of the box. The framework gives you the building blocks, but you have to wire up the navigation yourself. That's where libraries come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Navigation Is Such a Big Deal in React Native
&lt;/h2&gt;

&lt;p&gt;React Native apps are essentially single-page applications. Everything runs in one JavaScript thread, rendered to native views. There's no browser doing the heavy lifting of managing history or transitions. You're responsible for it all.&lt;/p&gt;

&lt;p&gt;Bad navigation kills good apps. A janky transition, a broken back button, a modal that doesn't dismiss properly, losing scroll position on a tab switch, these are the things that make users delete an app. Navigation isn't just a developer concern, it's directly tied to how polished your product feels.&lt;/p&gt;

&lt;p&gt;On top of that, navigation affects how your code is organized. If your navigation logic is scattered, your codebase gets messy fast. Passing params from screen to screen starts feeling like passing notes in class, chaotic and fragile. The navigation library you choose will shape not just the user experience but the entire architecture of your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Story of React Navigation
&lt;/h2&gt;

&lt;p&gt;React Navigation showed up around 2017 and quickly became the community standard for React Native navigation. Before it, developers were using a library called Navigator that came built into React Native, and honestly, it was rough. It was hard to customize, the API was clunky, and it didn't support a lot of the patterns native apps needed.&lt;/p&gt;

&lt;p&gt;React Navigation came in with a JavaScript-based approach, meaning the navigation logic runs entirely in JS rather than relying on native modules. This made it super flexible. You could customize everything. Stack navigators, tab navigators, drawer navigators, modal screens, you could compose all of these together however you needed.&lt;/p&gt;

&lt;p&gt;Over the years it matured a lot. React Navigation v5 brought a component-based API that felt much more React-like. v6 improved performance and added better TypeScript support. By the time v7 rolled around, it had become incredibly stable and powerful, with proper native stack support through react-native-screens, smooth gesture handling, and deep link support.&lt;/p&gt;

&lt;p&gt;Today React Navigation is still the backbone of a huge portion of React Native apps in production. It's battle-tested, well-documented, and has a massive community around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problems Developers Were Hitting
&lt;/h2&gt;

&lt;p&gt;Here's the thing though. Even as React Navigation got better, there were real pain points that never fully went away.&lt;/p&gt;

&lt;p&gt;The boilerplate was heavy. Every time you added a new screen, you had to register it with the navigator, define the route name as a string (or a typed constant if you were being careful), update your navigation types, and then navigate to it using &lt;code&gt;navigation.navigate('ScreenName')&lt;/code&gt;. If you had a large app with lots of screens, this got tedious fast, and it was easy to make typos or let things fall out of sync.&lt;/p&gt;

&lt;p&gt;Nested navigators were especially tricky. Imagine a tab navigator inside a stack navigator with a modal stack on top of that. Navigating from a screen deep inside one tab to a screen inside another tab required you to understand the full navigator hierarchy and use &lt;code&gt;navigation.navigate&lt;/code&gt; with just the right combination of route names. One wrong step and things broke in subtle ways.&lt;/p&gt;

&lt;p&gt;TypeScript support improved a lot but was never painless. You had to manually define types for every route and its params. It worked, but it was ceremony you had to maintain by hand.&lt;/p&gt;

&lt;p&gt;Deep linking required a separate configuration object where you mapped URL patterns to route names. So your routing logic was in one place, but your deep link config was somewhere else entirely. Keeping them in sync was a real maintenance burden.&lt;/p&gt;

&lt;p&gt;For small apps, none of this is a big deal. But as apps grow, you start to feel it. New developers joining the team had to understand both the navigator structure and the routing types before they could comfortably add a new screen. That's friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Expo Router: Why It Was Built
&lt;/h2&gt;

&lt;p&gt;Expo Router launched in 2023 and reached a stable v2 in late 2023, with v3 and v4 following through 2024 and 2025. The idea behind it was borrowed directly from the web. Specifically, from Next.js.&lt;/p&gt;

&lt;p&gt;If you've used Next.js, you know how good file-based routing feels. You create a file at &lt;code&gt;pages/about.tsx&lt;/code&gt;, and boom, there's a route at &lt;code&gt;/about&lt;/code&gt;. No configuration, no registration, just a file. Expo Router brings that same mental model to React Native.&lt;/p&gt;

&lt;p&gt;The key insight the Expo team had was this: your file system already knows about your screens. Why make developers maintain a separate navigation config that duplicates that information? Let the file system be the source of truth.&lt;/p&gt;

&lt;p&gt;It's also important to understand that Expo Router is not a replacement for React Navigation at the infrastructure level. Under the hood, Expo Router is built on top of React Navigation. It uses the same navigators, the same gesture handling, the same native stack. What it adds is a layer of abstraction that manages the routing config automatically based on your folder structure. So when you use Expo Router, you're not leaving React Navigation behind, you're using a smarter way to configure it.&lt;/p&gt;

&lt;h2&gt;
  
  
  File-Based Routing Explained Simply
&lt;/h2&gt;

&lt;p&gt;Here's the core concept. In Expo Router, you have an &lt;code&gt;app/&lt;/code&gt; directory. Every file you put in there becomes a screen. The file path becomes the route path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  index.tsx          -&amp;gt; /  (home screen)
  about.tsx          -&amp;gt; /about
  settings.tsx       -&amp;gt; /settings
  profile/
    index.tsx        -&amp;gt; /profile
    edit.tsx         -&amp;gt; /profile/edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No navigator file to update. No route name strings to type. The file structure tells the router everything it needs to know.&lt;/p&gt;

&lt;p&gt;Navigating between screens uses the &lt;code&gt;Link&lt;/code&gt; component or the &lt;code&gt;router&lt;/code&gt; object, and the paths you use match your file structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/profile/edit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Edit Profile&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or programmatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/profile/edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because routes are paths (strings derived from your folder structure) rather than arbitrary route names, you get a lot of benefits automatically. TypeScript can infer types from your file structure using Expo Router's typed routes feature. Deep links work out of the box because your routes are already path-based. Sharing links between web and mobile becomes much simpler when both are using the same routing system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cgayx3qkgxi1bekdh40.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cgayx3qkgxi1bekdh40.png" alt="File Structure to Screen Mapping" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Layouts and Nested Layouts in Expo Router
&lt;/h2&gt;

&lt;p&gt;One of the most powerful things in Expo Router is the layout system. In each folder, you can have a special file called &lt;code&gt;_layout.tsx&lt;/code&gt;. This file wraps all the screens inside that folder and lets you define shared UI or navigator configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  _layout.tsx        &amp;lt;- root layout (wraps everything)
  (tabs)/
    _layout.tsx      &amp;lt;- tab navigator setup
    index.tsx        &amp;lt;- first tab
    explore.tsx      &amp;lt;- second tab
  (auth)/
    _layout.tsx      &amp;lt;- auth flow layout
    login.tsx
    signup.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root &lt;code&gt;_layout.tsx&lt;/code&gt; might set up things like a theme provider, a toast notification system, or your authentication context. The &lt;code&gt;(tabs)/_layout.tsx&lt;/code&gt; defines the tab bar and which screens are tabs. The &lt;code&gt;(auth)/_layout.tsx&lt;/code&gt; might handle redirecting already-authenticated users away from the login screen.&lt;/p&gt;

&lt;p&gt;The parentheses in &lt;code&gt;(tabs)&lt;/code&gt; and &lt;code&gt;(auth)&lt;/code&gt; are a special Expo Router convention called "route groups." The folder name in parentheses is not included in the URL. It's just organizational. You're grouping screens logically without affecting the route paths.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;(tabs)/index.tsx&lt;/code&gt; has the route &lt;code&gt;/&lt;/code&gt; not &lt;code&gt;/(tabs)/&lt;/code&gt;. This is useful for organizing your code without creating weird nested URLs.&lt;/p&gt;

&lt;p&gt;Shared layouts mean that if you're inside the tabs navigator and you navigate between tabs, the tab bar stays put and only the content area re-renders. You get this for free just by structuring your files correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protected Routes and Auth Flows
&lt;/h2&gt;

&lt;p&gt;Authentication routing is something every real app has to deal with, and it's one of the areas where Expo Router shines with a clean pattern.&lt;/p&gt;

&lt;p&gt;The idea is simple. Inside your &lt;code&gt;(auth)/_layout.tsx&lt;/code&gt;, you check if the user is logged in. If they are, you redirect them to the main app. If not, you let them through to the login or signup screen. Inside your &lt;code&gt;(tabs)/_layout.tsx&lt;/code&gt; or your root layout, you do the opposite. If the user isn't logged in, redirect them to &lt;code&gt;/login&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's roughly what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/(auth)/_layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Redirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/hooks/useAuth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AuthLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isLoggedIn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Redirect&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/(tabs)"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/(tabs)/_layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Redirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tabs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/hooks/useAuth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TabsLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isLoggedIn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Redirect&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Tabs&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is clean, explicit, and colocated with the screens it protects. In React Navigation, you'd typically handle this through conditional rendering of different navigator trees or through a more complex navigation state management setup. Both work, but the Expo Router pattern feels more intuitive to reason about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszuf1cozy2oizduj2rls.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszuf1cozy2oizduj2rls.png" alt="Protected Route Auth Flow" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance: Let's Be Honest About What Actually Matters
&lt;/h2&gt;

&lt;p&gt;This is where a lot of comparisons get misleading. People throw around "Expo Router is faster" or "React Navigation has less overhead" without really qualifying what they mean. Let's break it down properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bundle behavior:&lt;/strong&gt; Expo Router supports automatic code splitting when used with Expo's Metro bundler in web mode. Each route can be lazily loaded, meaning the JavaScript for a screen only loads when the user navigates to it. In native mode (iOS/Android), this matters less because the whole bundle is bundled at build time anyway. React Navigation loads all screens eagerly by default, though you can manually implement lazy loading with the &lt;code&gt;lazy&lt;/code&gt; prop on tab navigators.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Navigation transitions:&lt;/strong&gt; Both approaches produce the same transition quality because Expo Router uses React Navigation's navigators under the hood. The native stack transitions you get from &lt;code&gt;react-native-screens&lt;/code&gt; are the same regardless of which tool you use to configure them. There's no meaningful difference here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer workflow speed:&lt;/strong&gt; This is where Expo Router genuinely wins. Adding a new screen is creating a file. No config update, no type declaration, no route string to maintain. With Expo Router's typed routes, your IDE knows all valid route paths and will autocomplete them. This reduces cognitive load and bugs, which might not be a "performance" metric in the benchmarking sense, but it absolutely speeds up how fast you ship features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime overhead:&lt;/strong&gt; Expo Router adds a thin abstraction layer on top of React Navigation. In practice, this overhead is negligible for any real-world app. You're not going to notice it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzml9gw8dlljenr2tuhwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzml9gw8dlljenr2tuhwe.png" alt="Architecture Comparison" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience: The Real Comparison
&lt;/h2&gt;

&lt;p&gt;If you're a beginner just getting into React Native, Expo Router is genuinely more approachable. The mental model is simpler. Create a file, it's a screen. Navigate using a path. The concept maps directly to things you might already know from web development. You spend less time learning the framework and more time building the actual app.&lt;/p&gt;

&lt;p&gt;For teams, file-based routing scales really nicely. When a new developer joins, they can understand the app's screen structure just by looking at the folder tree. You don't need to read a navigator config file to figure out what screens exist. This is a real benefit as teams grow.&lt;/p&gt;

&lt;p&gt;For TypeScript users, Expo Router's typed routes (which you enable with &lt;code&gt;"typedRoutes": true&lt;/code&gt; in your Expo config) give you end-to-end type safety on your paths. &lt;code&gt;router.push('/profle/edit')&lt;/code&gt; will show a type error if you typo the path. That alone has saved me from embarrassing bugs.&lt;/p&gt;

&lt;p&gt;React Navigation is not bad at developer experience, not at all. It's very explicit, which has its own value. You always know exactly how your navigators are composed because you wrote every line of it. For developers who prefer explicit configuration over convention, this feels more comfortable. There's also a huge amount of community knowledge, Stack Overflow answers, blog posts, and Discord help available for React Navigation compared to Expo Router.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scalability for Large Applications
&lt;/h2&gt;

&lt;p&gt;Let's say you're building something serious. A fintech app, a social platform, a healthcare tool. Something with dozens of screens, multiple user roles, complex flows, and a team of ten engineers working on it simultaneously.&lt;/p&gt;

&lt;p&gt;With React Navigation at that scale, you'll likely have a main navigator file that's quite long, or you'll split navigators across multiple files and import them into each other. Neither is bad, but the manual nature of it means there's potential for it to drift and become hard to follow. Strong TypeScript discipline helps, but it's discipline you have to actively maintain.&lt;/p&gt;

&lt;p&gt;With Expo Router at that scale, the folder structure does most of the organizational work for you. You can create nested groups, separate flows by folder, add route-specific layouts. The structure of the app is visible from the file system. New engineers can get oriented faster.&lt;/p&gt;

&lt;p&gt;That said, Expo Router's conventions can feel constraining for non-standard navigation patterns. If you have a very custom navigation UX, like a multi-step wizard with complex back behavior, or a non-standard transition system, you might find yourself working around Expo Router's conventions rather than with them. React Navigation gives you more raw control in those cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World App Folder Structure
&lt;/h2&gt;

&lt;p&gt;Here's what a production-ready Expo Router project might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  _layout.tsx              &amp;lt;- root (providers, fonts, splash)
  +not-found.tsx           &amp;lt;- 404/unknown route handler

  (auth)/
    _layout.tsx            &amp;lt;- redirect if already logged in
    login.tsx
    signup.tsx
    forgot-password.tsx

  (app)/
    _layout.tsx            &amp;lt;- redirect if not logged in
    (tabs)/
      _layout.tsx          &amp;lt;- tab bar config
      index.tsx            &amp;lt;- Home tab
      explore.tsx          &amp;lt;- Explore tab
      inbox.tsx            &amp;lt;- Inbox tab
      profile.tsx          &amp;lt;- Profile tab

    post/
      [id].tsx             &amp;lt;- Dynamic route: /post/123
      [id]/comments.tsx    &amp;lt;- Nested: /post/123/comments

    settings/
      index.tsx
      notifications.tsx
      privacy.tsx
      account.tsx

  modal/
    image-preview.tsx      &amp;lt;- Modal screens
    share-sheet.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;[id]&lt;/code&gt; syntax is for dynamic routes where the segment changes, like a user ID or post ID. &lt;code&gt;router.push('/post/123')&lt;/code&gt; would render &lt;code&gt;post/[id].tsx&lt;/code&gt; with &lt;code&gt;id = "123"&lt;/code&gt; accessible via &lt;code&gt;useLocalSearchParams()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Compare this to the equivalent React Navigation setup. You'd have a &lt;code&gt;navigation/&lt;/code&gt; folder with files like &lt;code&gt;RootNavigator.tsx&lt;/code&gt;, &lt;code&gt;AuthStack.tsx&lt;/code&gt;, &lt;code&gt;AppTabs.tsx&lt;/code&gt;, &lt;code&gt;SettingsStack.tsx&lt;/code&gt;, and a &lt;code&gt;types.ts&lt;/code&gt; file defining all the route params. It works perfectly well, but it's more code to write and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Companies and Teams Are Actually Choosing
&lt;/h2&gt;

&lt;p&gt;In 2026, if you're starting a new project and using Expo (which is now the default recommendation from the React Native team itself), Expo Router is the default choice that comes out of the box. The &lt;code&gt;npx create-expo-app&lt;/code&gt; template uses Expo Router. The official Expo documentation is written around Expo Router. New Expo features like server-side rendering, universal links, and the web support all integrate most cleanly with Expo Router.&lt;/p&gt;

&lt;p&gt;Larger enterprise teams that have existing React Navigation codebases are generally not migrating. The cost-benefit doesn't make sense for stable production apps. If it works, it works.&lt;/p&gt;

&lt;p&gt;Startups and new projects lean toward Expo Router for the DX benefits and because it positions them well for Expo's growing ecosystem.&lt;/p&gt;

&lt;p&gt;Teams doing pure bare React Native (without Expo) still predominantly use React Navigation directly because Expo Router is designed for the Expo ecosystem. You can technically use it outside Expo, but it's not the intended use case and the experience gets rougher.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to Use Expo Router
&lt;/h2&gt;

&lt;p&gt;Expo Router is genuinely not the right choice in some situations, and being honest about this matters.&lt;/p&gt;

&lt;p&gt;If your project is bare React Native without Expo tooling, stick with React Navigation. Expo Router has dependencies on the Expo build system and Metro configuration. It can work outside of Expo, but you'll hit friction, and the community support for that setup is thin.&lt;/p&gt;

&lt;p&gt;If you have highly custom navigation UX that doesn't map neatly to the file-based paradigm, you might find yourself fighting the conventions. Things like multi-step onboarding flows with complex skip logic, paginated full-screen flows, or deeply custom transition animations are easier to achieve with the direct control React Navigation gives you.&lt;/p&gt;

&lt;p&gt;If your team is more comfortable with explicit configuration and dislikes "magic," React Navigation's transparency might be worth more than Expo Router's convenience. Good code is code your team understands and trusts.&lt;/p&gt;

&lt;p&gt;If you're maintaining an existing React Navigation codebase that's working well, don't migrate for the sake of it. The grass is not always greener, and migrations introduce risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  When React Navigation Still Makes More Sense
&lt;/h2&gt;

&lt;p&gt;React Navigation is not a legacy tool. It's actively maintained, has a huge community, and is the right choice in plenty of real scenarios.&lt;/p&gt;

&lt;p&gt;When you need very fine-grained control over the navigation state, like manually manipulating the navigation stack or implementing complex reset behaviors, React Navigation's imperative API gives you exactly what you need.&lt;/p&gt;

&lt;p&gt;When you're building a library or a reusable component that other React Native apps will consume, you probably don't want an Expo Router dependency in your package. React Navigation is the neutral, universal choice.&lt;/p&gt;

&lt;p&gt;When your team has deep React Navigation expertise and the project is time-sensitive, using what you know well beats learning new conventions under deadline pressure.&lt;/p&gt;

&lt;p&gt;When you're targeting platforms or environments where Expo's toolchain isn't suitable, React Navigation is the safe choice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx59ye0j5k9v43f5w3ast.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx59ye0j5k9v43f5w3ast.png" alt="Which Should You Use?" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;Here's the honest summary. Expo Router and React Navigation are not really competitors in the way the title of this article might imply. Expo Router is built on React Navigation. Choosing Expo Router means choosing a better configuration experience layered on top of the same navigation infrastructure.&lt;/p&gt;

&lt;p&gt;For most new apps being built in 2026 with the Expo ecosystem, Expo Router is the better default. The file-based mental model is intuitive, the TypeScript integration is excellent, and it makes your codebase easier for teams to navigate (pun intended).&lt;/p&gt;

&lt;p&gt;For existing apps, apps outside the Expo ecosystem, or teams that value explicit configuration, React Navigation remains a completely solid choice that's not going anywhere.&lt;/p&gt;

&lt;p&gt;The best navigation setup is the one your team understands, your users don't notice, and your codebase doesn't make you dread opening. Use that one.&lt;/p&gt;




&lt;p&gt;Hope you liked this blog. If there’s any mistake or something I can improve, do tell me. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, I post more stuff there.&lt;/p&gt;

</description>
      <category>expo</category>
      <category>reactnative</category>
      <category>reactnavigation</category>
    </item>
    <item>
      <title>How Instagram Stores Reels, Photos, and Drafts Behind the Scenes</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Sun, 31 May 2026 09:11:22 +0000</pubDate>
      <link>https://dev.to/satyasootar/how-instagram-stores-reels-photos-and-drafts-behind-the-scenes-1jeb</link>
      <guid>https://dev.to/satyasootar/how-instagram-stores-reels-photos-and-drafts-behind-the-scenes-1jeb</guid>
      <description>&lt;p&gt;You pull out your phone, record a 30-second Reel, add a trending audio, and hit "Save Draft" because you are not ready to post yet. You lock your phone, forget about it, come back three days later, and the draft is right there waiting for you. Perfectly intact. Nothing lost. But have you ever stopped to wonder how that actually works? Where did that video go? Who is holding onto it? And what happens the moment you finally hit "Share"?&lt;/p&gt;

&lt;p&gt;This one is for you if you are building mobile apps, studying system design, or you are just genuinely curious about how apps like Instagram handle media at scale. We are going to walk through the full journey, from the moment you tap record to the moment your Reel appears on someone's feed in another country.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Social Media Apps Need a Special Approach to Media Storage
&lt;/h2&gt;

&lt;p&gt;Regular text is tiny. A tweet, a caption, a comment, all of that is just a few hundred bytes at most. But media is a completely different animal. A single uncompressed 10-second video clip on a modern smartphone can easily be 200MB or more. A high-resolution photo can be 10MB to 15MB. Now multiply that by billions of users creating content every single day.&lt;/p&gt;

&lt;p&gt;Instagram reportedly has over 2 billion monthly active users. If even a small fraction of them upload media daily, you are talking about petabytes of data moving through the system every 24 hours. This is why social media apps cannot afford to treat media storage the way a basic web form treats a file upload. They need a layered, thoughtful architecture that balances speed, cost, reliability, and user experience all at once.&lt;/p&gt;

&lt;p&gt;The core challenge is this: users expect everything to feel instant, but the actual work happening under the hood is enormous.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Journey Begins: Recording a Reel on Your Device
&lt;/h2&gt;

&lt;p&gt;Let's start from the very beginning. When you open Instagram and start recording a Reel, the video is being captured by your phone's camera hardware and written to a temporary buffer in the app's local storage. This temporary storage lives inside what is called the app's sandbox, a private directory on your device that only Instagram can read and write to.&lt;/p&gt;

&lt;p&gt;At this point nothing has gone to Instagram's servers yet. The video exists only on your phone. This is intentional. Uploading every frame as you record would be wasteful, slow, and would drain your battery. Instead, the app collects the raw footage locally first.&lt;/p&gt;

&lt;p&gt;Your device is already doing some lightweight processing during recording, things like stabilizing the footage and encoding it into a workable format like H.264 or HEVC (H.265). These are video compression standards that dramatically reduce file size while keeping quality acceptable. Think of it as the video being "packed" efficiently before anything else happens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8hrxvl9edqdk6gfhriea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8hrxvl9edqdk6gfhriea.png" alt="Capturing video flow" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When You Save a Draft
&lt;/h2&gt;

&lt;p&gt;Here is where things get interesting. When you tap "Save Draft," Instagram does not just keep the video floating around in memory. It writes the file to a more permanent location within the app's local storage. On Android this is typically in the app's internal storage or cache directory. On iOS it is in a similar sandboxed directory managed by the operating system.&lt;/p&gt;

&lt;p&gt;But the video file alone is not enough. A draft also needs to remember everything else you set up: the caption you started typing, the audio you chose, the stickers, the trim points on the video, the cover frame you selected. All of that metadata gets saved alongside the video, usually as a small structured data file, often in JSON or a similar lightweight format.&lt;/p&gt;

&lt;p&gt;This combination, the video file plus its metadata file, is what makes a draft "complete." When you come back to it days later, the app reads both the video and the metadata and reconstructs your edit session exactly where you left it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How drafts survive app restarts:&lt;/strong&gt; This is a question that comes up a lot. The answer is simple but important. Because the draft is written to persistent local storage (not just held in memory), it survives the app being closed, your phone restarting, or even the app updating. Memory gets wiped when a process dies. Disk does not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04cy60qy2birlqdmjr3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04cy60qy2birlqdmjr3y.png" alt="Draft Storage Architecture" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Storage vs Cloud Storage: Two Very Different Jobs
&lt;/h2&gt;

&lt;p&gt;People often think of "storage" as one thing. But in the world of mobile apps, local storage and cloud storage serve completely different purposes and have different tradeoffs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local storage&lt;/strong&gt; lives on your device. It is fast because there is no network involved. Reading a file from your phone's storage takes milliseconds. Writing to it is equally quick. The downside is obvious: it is limited to the size of your device, and if you lose your phone or clear the app's data, it is gone. Drafts live here because they are temporary and personal to you and your device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloud storage&lt;/strong&gt; lives on servers in data centers, usually spread across multiple geographic regions for redundancy. When you finally post your Reel, the video gets uploaded from your device to Instagram's cloud infrastructure. Once it is there, it can be accessed from anywhere, backed up across multiple servers, and delivered to users around the world. The tradeoff is that accessing it requires a network connection and takes more time.&lt;/p&gt;

&lt;p&gt;A well-designed app like Instagram uses both strategically. Local storage for speed and offline capability, cloud storage for durability and shareability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0k8qe0g0ly2kkojnpkzc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0k8qe0g0ly2kkojnpkzc.png" alt="Local vs Cloud Storage Split" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Upload Pipeline: What Actually Happens When You Hit "Share"
&lt;/h2&gt;

&lt;p&gt;Tapping "Share" kicks off one of the more complex workflows in the entire app. A lot happens in those few seconds before you see the "Your Reel has been shared" confirmation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chunked uploading&lt;/strong&gt; is the first key concept here. Instead of sending the entire video file as one giant request, Instagram's client splits it into smaller pieces called chunks. Each chunk is uploaded separately. If your connection drops halfway through, you do not have to start over from the beginning. The app just resumes from the last successfully uploaded chunk. This is called resumable uploading, and it is critical for large files on mobile networks where connections are unreliable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upload queuing&lt;/strong&gt; is also happening here. Your video gets placed in a queue so the upload can continue even if you navigate away from the screen or switch to another app. This is handled by a background process that the operating system allows to keep running for a period of time even when the app is not in the foreground.&lt;/p&gt;

&lt;p&gt;Once all the chunks are received on Instagram's servers, they are reassembled and verified. A checksum, basically a mathematical fingerprint of the file, is compared to ensure nothing was corrupted during transit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuu6k18vv8iy2jrd0927y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuu6k18vv8iy2jrd0927y.png" alt="Upload pipeline" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Media Processing and Compression: Making Videos Work for Everyone
&lt;/h2&gt;

&lt;p&gt;Here is something important to understand: the raw video you recorded on your iPhone 15 Pro is not what plays on your friend's older Android device. Instagram processes and re-encodes every piece of media it receives.&lt;/p&gt;

&lt;p&gt;When your video hits Instagram's servers, it enters a media processing pipeline. This pipeline does several things. It transcodes the video, meaning it converts it into multiple different formats and quality levels. There is typically a high-quality version for users on fast WiFi connections, a medium-quality version for 4G, and a low-quality version for slower connections. This technique is called adaptive bitrate streaming.&lt;/p&gt;

&lt;p&gt;The processing pipeline also handles things like rotating the video if the metadata says it was recorded sideways, normalizing audio levels, and stripping out any metadata from the file that could expose sensitive information (like the exact GPS coordinates baked into a photo).&lt;/p&gt;

&lt;p&gt;For photos, a similar process runs. The original image is compressed to reduce file size, resized to standard dimensions, and converted to a format optimized for the web and mobile display. Instagram famously uses JPEG compression quite aggressively, which is why images sometimes look slightly different after upload compared to the originals on your camera roll.&lt;/p&gt;

&lt;p&gt;All of this processing happens asynchronously in the background after your upload completes. It is one reason why sometimes you see "Processing..." on a video before it becomes fully viewable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thumbnail Generation and Previews
&lt;/h2&gt;

&lt;p&gt;Before anyone watches your Reel, they see a thumbnail. That tiny preview image is doing a lot of work. It needs to be visually compelling enough to make someone stop scrolling, and it needs to load instantly even on a slow connection.&lt;/p&gt;

&lt;p&gt;Instagram automatically generates a thumbnail from your video, usually pulling a frame from somewhere near the beginning or letting you choose your own cover frame. That thumbnail image gets stored separately from the video itself and treated as a first-class asset. When someone's feed loads, their app downloads thumbnails first because they are much smaller than the actual videos. This is what creates the experience of seeing still images in your feed before the videos start playing.&lt;/p&gt;

&lt;p&gt;Low-resolution preview images, sometimes called "blur-up" previews or LQIP (Low Quality Image Placeholders), are generated too. These are tiny, low-fidelity versions of the thumbnail, maybe 20 to 40 pixels wide, that can be embedded directly in the app's response data. They display instantly before the real thumbnail even finishes loading, giving the illusion of content appearing immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching: Why Your Feed Loads So Fast on Reopen
&lt;/h2&gt;

&lt;p&gt;You have probably noticed that when you close Instagram and reopen it quickly, your feed appears almost instantly. That is caching at work.&lt;/p&gt;

&lt;p&gt;A cache is a local store of data that the app saves to your device so it does not have to re-download the same content over and over. When you scroll through your feed, Instagram is downloading posts and storing them in a cache on your device. The next time you open the app or scroll back up, it reads from that cache instead of hitting the server again.&lt;/p&gt;

&lt;p&gt;The interesting challenge with caching is knowing when to invalidate it, meaning when to throw away the cached version and fetch a fresh one. If someone edits their post or deletes it, you do not want to keep showing the old cached version indefinitely. Apps handle this in various ways, like setting expiry times on cached content or using version identifiers that change whenever content is updated.&lt;/p&gt;

&lt;p&gt;Caching also happens at multiple levels. The app caches images and videos on your device. But there are also caches sitting at the server infrastructure level, which brings us to CDNs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fplcbavuv8nuigpzvd6u6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fplcbavuv8nuigpzvd6u6.png" alt="Cache Lifecycle" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Delivery Networks: How Your Reel Reaches Someone in Japan
&lt;/h2&gt;

&lt;p&gt;Once your Reel is processed and live on Instagram's servers, it needs to be delivered to users potentially on the other side of the world. Sending every video request all the way back to Instagram's primary data centers (which might be in the US) for every single viewer would be unbearably slow and expensive.&lt;/p&gt;

&lt;p&gt;This is where a CDN, or Content Delivery Network, comes in. A CDN is a globally distributed network of servers placed in data centers all around the world. When Instagram publishes your Reel, the video gets copied out to CDN servers in dozens or hundreds of geographic locations.&lt;/p&gt;

&lt;p&gt;When someone in Tokyo opens their feed and your Reel starts playing, their app is actually fetching the video from a CDN server located in Asia, not from a server in Virginia. The physical distance between the user and the server is massively reduced, which means lower latency, faster load times, and a smoother playback experience.&lt;/p&gt;

&lt;p&gt;CDNs also act as a buffer. If your Reel suddenly goes viral and a million people try to watch it at the same time, the CDN absorbs that traffic load across all its distributed nodes instead of hammering Instagram's central infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4azn7777appm2tmloz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4azn7777appm2tmloz3.png" alt="CDN Content Delivery Flow" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing the Whole System: Storage, Performance, and User Experience
&lt;/h2&gt;

&lt;p&gt;Pulling all of this together is not just a technical challenge. It is also a product and business challenge. Every megabyte of video stored costs money. Every CDN transfer costs money. Instagram has to make constant decisions about what to store, for how long, in how many copies, and at what quality level.&lt;/p&gt;

&lt;p&gt;Popular content that gets viewed millions of times is worth keeping in multiple high-quality copies across many CDN nodes because the cost of delivery is offset by the engagement it drives. Old content that barely gets viewed might get moved to cheaper, slower "cold storage" over time. This is a concept called tiered storage: hot storage for frequently accessed content, cold storage for content that is rarely accessed.&lt;/p&gt;

&lt;p&gt;On the user experience side, all of these infrastructure decisions translate into choices about how the app behaves. How long does Instagram keep your drafts locally before offering to clear them? How aggressively does it cache videos on your device before managing your storage? When does it decide to start preloading the next video in your feed before you even scroll to it? These are all product decisions backed by the storage and delivery architecture underneath.&lt;/p&gt;

&lt;p&gt;The best mobile apps treat storage as a first-class concern, not an afterthought. Every byte has a cost, and every delay has a user impact. The fact that Instagram can make recording, editing, saving, uploading, processing, and delivering a video feel seamless across billions of devices around the world is genuinely one of the more impressive engineering achievements of the modern internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;So the next time you save a Reel as a draft, here is what you now know is happening. The video is sitting in your device's local storage inside Instagram's sandboxed directory, paired with a metadata file that remembers every edit you made. When you finally post it, it gets chunked and uploaded to Instagram's servers over a resumable upload session. On the server side it gets processed, compressed, transcoded into multiple quality levels, and thumbnails get generated. The processed files get pushed out to CDN nodes around the world. And when your followers open the app, their clients fetch the video from the closest CDN node while also checking their local cache to avoid unnecessary downloads.&lt;/p&gt;

&lt;p&gt;Every step in that chain is solving a real engineering problem. Speed, reliability, scale, cost, and user experience are all in tension with each other, and every design decision is a tradeoff between them. Understanding these tradeoffs is what separates engineers who just build features from engineers who build systems that actually hold up under pressure.&lt;/p&gt;




&lt;p&gt;Hope you liked this blog. If there’s any mistake or something I can improve, do tell me. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, I post more stuff there.&lt;/p&gt;

</description>
      <category>expo</category>
      <category>instagram</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>How WhatsApp Works Without Internet: Offline Messaging and Sync Explained</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Sun, 31 May 2026 07:45:48 +0000</pubDate>
      <link>https://dev.to/satyasootar/how-whatsapp-works-without-internet-offline-messaging-and-sync-explained-3nle</link>
      <guid>https://dev.to/satyasootar/how-whatsapp-works-without-internet-offline-messaging-and-sync-explained-3nle</guid>
      <description>&lt;p&gt;You're on the subway, signal drops to zero, and you still tap send on that message. A single grey tick appears. Your phone didn't throw an error, the app didn't crash, and somehow that message will reach your friend the moment you get back above ground. How does that actually work? Let's break it down.x&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Messaging Apps Need Offline Support
&lt;/h2&gt;

&lt;p&gt;Think about how you actually use your phone. You're in an elevator, on a flight, driving through a tunnel, or just in a building with terrible WiFi. Connectivity is not a constant, it's a privilege that comes and goes. If WhatsApp only worked when you had a perfect internet connection, it would be practically useless in the real world.&lt;/p&gt;

&lt;p&gt;This is why modern messaging apps are built with what engineers call an &lt;strong&gt;offline-first architecture&lt;/strong&gt;. The core idea is simple: the app should work as close to normal as possible even without internet, and quietly reconcile everything with the server the moment connection returns. The user should barely notice the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Happens When You Hit Send Without Internet
&lt;/h2&gt;

&lt;p&gt;Let's start with the simplest scenario: airplane mode.&lt;/p&gt;

&lt;p&gt;You open WhatsApp, type "I'll be there in 10 minutes", and hit send. The app does not wait for a server response. It immediately displays your message in the chat with a single grey clock or tick icon. From your perspective the message is sent. But technically, it hasn't left your device yet.&lt;/p&gt;

&lt;p&gt;What WhatsApp just did is write your message to &lt;strong&gt;local storage&lt;/strong&gt; on your phone. It saved it as a pending item in a local queue, stamped it with a timestamp, assigned it a local ID, and tagged it with a status of "pending". The UI renders it immediately because it's reading from local storage, not from a server. That's why you see it instantly.&lt;/p&gt;

&lt;p&gt;This pattern is called &lt;strong&gt;optimistic UI&lt;/strong&gt;. The app assumes the best case scenario, shows you the result immediately, and handles the actual network operation in the background.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84zqfddce0shxab2nxy9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84zqfddce0shxab2nxy9.png" alt="Flow diagram for wp messaging" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Storage and Message Persistence
&lt;/h2&gt;

&lt;p&gt;Your phone is running a tiny database. On Android, WhatsApp uses &lt;strong&gt;SQLite&lt;/strong&gt;, a lightweight relational database that lives right on your device. On iOS it works similarly. Every message you send or receive gets stored in this local database, including messages you haven't sent yet.&lt;/p&gt;

&lt;p&gt;This local database holds a lot more than just text. It stores the message content, who sent it, who it's going to, what time it was created, what its current delivery status is, and whether it's been queued for sending. Think of it like a local source of truth that the app reads from first before even thinking about the internet.&lt;/p&gt;

&lt;p&gt;This is also why WhatsApp can show you your entire chat history even when you're completely offline. You're not loading messages from a server, you're reading from the database on your own device.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Message Queue: Your Phone's Outbox
&lt;/h2&gt;

&lt;p&gt;When a message is saved locally and there's no internet, it gets placed in what's called a &lt;strong&gt;message queue&lt;/strong&gt;. This is basically an ordered list of things that need to be sent to the server when connectivity comes back.&lt;/p&gt;

&lt;p&gt;The queue respects order. If you send three messages while offline, they go into the queue in sequence: message 1, message 2, message 3. When the internet returns, they get sent in that exact order. This matters because message ordering is something users care about deeply and it would be confusing if your messages arrived out of sequence.&lt;/p&gt;

&lt;p&gt;The queue also handles retries. If the app tries to send a message and the connection drops mid-attempt, it doesn't give up. It retries with a strategy called &lt;strong&gt;exponential backoff&lt;/strong&gt;, which means it waits a little, tries again, waits a bit longer, tries again, and so on. This prevents the app from hammering the server repeatedly and draining your battery.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvape623p531lgzrx568n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvape623p531lgzrx568n.png" alt="flow 2" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Syncing When You Come Back Online
&lt;/h2&gt;

&lt;p&gt;This is where things get interesting. The moment your phone detects internet connectivity, a background process wakes up and starts the sync. &lt;/p&gt;

&lt;p&gt;Here's roughly what happens:&lt;br&gt;
First, the app works through the outgoing queue and uploads your pending messages to the WhatsApp server. Second, it asks the server if there are any incoming messages it missed while you were offline. The server has been holding those messages and delivers them now. Third, the local database gets updated with the new information and the UI refreshes to show everything.&lt;/p&gt;

&lt;p&gt;This process of having multiple places store data (your phone and the server) and then reconciling them is called &lt;strong&gt;eventual consistency&lt;/strong&gt;. You're not guaranteed that everyone sees the same state at the exact same millisecond. But you are guaranteed that eventually, once everyone is online, everything will be consistent. It's a deliberate tradeoff that prioritizes availability (the app keeps working offline) over perfect real-time accuracy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5h5hueo491iuecs9cnp5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5h5hueo491iuecs9cnp5.png" alt="Flow Diagram 3" width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivery States: What Those Ticks Actually Mean
&lt;/h2&gt;

&lt;p&gt;This is probably the most visible part of the whole system and one most people are curious about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One grey tick&lt;/strong&gt; means the message has been saved on your device and sent to WhatsApp's server. The server has it. But it hasn't been delivered to your recipient's device yet, maybe they're offline too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two grey ticks&lt;/strong&gt; means the message was successfully delivered to your recipient's device. Their phone has it. They may not have opened the chat though.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two blue ticks&lt;/strong&gt; means your recipient opened the chat and the message was displayed on their screen. Their app sent a read receipt back to the server, which forwarded it to you.&lt;/p&gt;

&lt;p&gt;Each of these is a distinct state in the database. The message record gets updated as it moves through these stages. If you're offline when someone reads your message, those blue ticks will appear the next time your phone connects and pulls the updated status from the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8l8a3bsue6rige6044d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8l8a3bsue6rige6044d.png" alt="Message delivery flow" width="799" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Media While Offline
&lt;/h2&gt;

&lt;p&gt;Text messages are tiny, a few bytes at most. But photos, videos, voice notes and documents are a different story. You can't realistically queue up a 50MB video and fire it off the moment you reconnect without the user noticing.&lt;/p&gt;

&lt;p&gt;Here's how WhatsApp handles it. When you attach a photo and hit send offline, the media file gets saved locally and tagged as "pending upload". The text metadata (who sent it, which chat, what time) goes into the queue just like a regular message. When connectivity returns, the media gets uploaded to WhatsApp's media servers first. Once the upload completes and WhatsApp gets a URL for that file, the message is then sent with a link pointing to that media.&lt;/p&gt;

&lt;p&gt;On the receiving end, your friend's phone gets the message with the media URL but doesn't automatically download the file. That's a separate download that happens over their connection, which is why you sometimes see a "tap to download" prompt rather than media appearing instantly. This keeps the sync process fast and lets users decide whether to download large files on mobile data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conflict Resolution and Message Ordering
&lt;/h2&gt;

&lt;p&gt;Here's a tricky scenario. What if two people in a group chat both send messages at almost the exact same time while one of them is on a spotty connection? Whose message comes first?&lt;/p&gt;

&lt;p&gt;WhatsApp uses server-side timestamps for final ordering. When your message reaches the server, the server stamps it with an authoritative time. That server timestamp becomes the canonical order. If messages arrive at the server out of order (because one person had a delay), the server figures out the right sequence and delivers them in the correct order to everyone.&lt;/p&gt;

&lt;p&gt;On your device, you might briefly see messages in a slightly different order before the sync catches up. This is eventually corrected, which is again that eventual consistency idea in action. The goal is that once everyone syncs, the conversation looks the same for everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability vs Real-Time: The Tradeoff
&lt;/h2&gt;

&lt;p&gt;There's a genuine tension here that's worth understanding. If you prioritize &lt;strong&gt;reliability&lt;/strong&gt; (messages always get through, the app never loses your data), you end up with some latency and complexity. If you prioritize &lt;strong&gt;real-time delivery&lt;/strong&gt; (messages arrive instantly with no local queuing), you lose resilience when the network is unreliable.&lt;/p&gt;

&lt;p&gt;WhatsApp has clearly chosen reliability. The local queue, the persistent database, the retry logic, all of this adds complexity but means your message almost never gets lost. The tradeoff is that delivery isn't always instant and the two-tick system exists precisely to tell you where in the pipeline your message currently sits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Offline-First Makes the Experience Feel Smooth
&lt;/h2&gt;

&lt;p&gt;Here's the thing about offline-first design that isn't immediately obvious. It actually makes the app feel faster even when you do have good internet. Because the app reads from local storage first and updates the UI immediately without waiting for server confirmation, everything feels snappy. You're not sitting there waiting for a round trip to a server in some data center. You type, you send, you see it. Done.&lt;/p&gt;

&lt;p&gt;The network operation happens quietly in the background. When the server confirms, the status updates. But the user experience is driven by local state, not network state. This is a fundamental design philosophy and it's the reason modern apps like WhatsApp, iMessage, and Telegram feel so much more responsive than older web-based messaging tools that required a live connection for every single action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;So next time you tap send in a tunnel, here's what's silently happening on your phone. Your message is written to a local SQLite database. The queue manager picks it up and holds it. The UI reads from the local database and shows you your message immediately. When the connection returns, the queue drains in order to the server, the server delivers it to your recipient, and both devices update their local databases with the new status. The ticks change color. Everything catches up.&lt;/p&gt;

&lt;p&gt;It's a beautifully engineered illusion of simplicity sitting on top of a surprisingly sophisticated system. And the next time someone says "I sent you a message, didn't you get it?", you can explain exactly why the answer might be more complicated than it sounds.&lt;/p&gt;




&lt;p&gt;Hope you liked this blog. If there’s any mistake or something I can improve, do tell me. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, I post more stuff there.&lt;/p&gt;

</description>
      <category>expo</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>I Spent a Weekend Hunting Through Linux's File System and Here's What I Found</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Fri, 29 May 2026 05:49:26 +0000</pubDate>
      <link>https://dev.to/satyasootar/i-spent-a-weekend-hunting-through-linuxs-file-system-and-heres-what-i-found-1gka</link>
      <guid>https://dev.to/satyasootar/i-spent-a-weekend-hunting-through-linuxs-file-system-and-heres-what-i-found-1gka</guid>
      <description>&lt;p&gt;Most people learn Linux by memorizing commands. &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;cd&lt;/code&gt;, &lt;code&gt;mkdir&lt;/code&gt;. But here's the thing, that barely scratches the surface of what's actually going on underneath. Linux is one of those systems where the more you dig, the more you realize the entire OS is basically a giant, structured collection of files that control everything. I decided to stop running commands blindly and actually go hunting. What I found was genuinely interesting.&lt;/p&gt;

&lt;p&gt;This blog covers my top discoveries from exploring the Linux file system, things I wish someone had explained to me when I was starting out. Not a command list. Just a deep dive into what's really going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before We Start: The Map You Need
&lt;/h2&gt;

&lt;p&gt;Here's how the top-level Linux file system is roughly laid out&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7217yw4eo4j5m1jwmsn6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7217yw4eo4j5m1jwmsn6.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1: &lt;code&gt;/etc/passwd&lt;/code&gt; Was Never Really About Passwords
&lt;/h2&gt;

&lt;p&gt;When you first hear "the password file," you assume it holds passwords. The name is right there. But open it and run &lt;code&gt;cat /etc/passwd&lt;/code&gt; on any modern Linux machine, and you'll see something like this for each user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;root&lt;/span&gt;:&lt;span class="n"&gt;x&lt;/span&gt;:&lt;span class="m"&gt;0&lt;/span&gt;:&lt;span class="m"&gt;0&lt;/span&gt;:&lt;span class="n"&gt;root&lt;/span&gt;:/&lt;span class="n"&gt;root&lt;/span&gt;:/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;bash&lt;/span&gt;
&lt;span class="n"&gt;john&lt;/span&gt;:&lt;span class="n"&gt;x&lt;/span&gt;:&lt;span class="m"&gt;1001&lt;/span&gt;:&lt;span class="m"&gt;1001&lt;/span&gt;:&lt;span class="n"&gt;John&lt;/span&gt; &lt;span class="n"&gt;Doe&lt;/span&gt;:/&lt;span class="n"&gt;home&lt;/span&gt;/&lt;span class="n"&gt;john&lt;/span&gt;:/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;x&lt;/code&gt; in the second field? That's where the password hash used to live, literally stored in plain text in early Unix systems. Which is terrifying. At some point, system designers realized having world-readable passwords was a bad idea, so they moved the actual hashed passwords into &lt;code&gt;/etc/shadow&lt;/code&gt;, which is only readable by root.&lt;/p&gt;

&lt;p&gt;The fields in &lt;code&gt;/etc/passwd&lt;/code&gt; tell you: username, password placeholder, UID (user ID), GID (group ID), full name, home directory, and default shell. This file is the backbone of user identity on Linux. Every time you log in, something reads this file to figure out who you are, where your home folder is, and which shell to launch.&lt;/p&gt;

&lt;p&gt;What I found interesting: system services like &lt;code&gt;www-data&lt;/code&gt;, &lt;code&gt;nobody&lt;/code&gt;, and &lt;code&gt;daemon&lt;/code&gt; are also listed here. These are not human users, they're service accounts created so that web servers and background processes don't have to run as root. It's a simple but clever way to limit damage if something gets compromised.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl40flvw8my2vucrz3bp7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl40flvw8my2vucrz3bp7.png" alt="Linux Authentication Flow" width="799" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2: &lt;code&gt;/etc/shadow&lt;/code&gt; Is Where the Real Secrets Live
&lt;/h2&gt;

&lt;p&gt;Once you're root, run &lt;code&gt;sudo cat /etc/shadow&lt;/code&gt;. Each line looks something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;john&lt;/span&gt;:$&lt;span class="m"&gt;6&lt;/span&gt;$&lt;span class="n"&gt;randomsalt&lt;/span&gt;$&lt;span class="n"&gt;longhashstring&lt;/span&gt;...:&lt;span class="m"&gt;19800&lt;/span&gt;:&lt;span class="m"&gt;0&lt;/span&gt;:&lt;span class="m"&gt;99999&lt;/span&gt;:&lt;span class="m"&gt;7&lt;/span&gt;:::
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking that down: the hash starts with &lt;code&gt;$6$&lt;/code&gt; which means SHA-512 (older systems used &lt;code&gt;$1$&lt;/code&gt; for MD5, which is now considered weak). After that you have the salt, then the actual hash. Then a bunch of numbers representing: days since last password change, minimum days before change allowed, maximum days before forced change, and warning days before expiry.&lt;/p&gt;

&lt;p&gt;This file exists specifically because &lt;code&gt;/etc/passwd&lt;/code&gt; has to be readable by everyone (programs need to look up usernames), but passwords obviously shouldn't be. So Linux splits the concern. Public info in one file, secret info in another, with strict permissions on the second one.&lt;/p&gt;

&lt;p&gt;The insight here is that Linux uses file permissions as a security boundary — not some fancy encryption layer, just strict ownership rules. Simple, elegant, effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  3: &lt;code&gt;/etc/hosts&lt;/code&gt; Is the Original DNS
&lt;/h2&gt;

&lt;p&gt;Before DNS servers existed, every machine on the internet had a manually updated file mapping hostnames to IP addresses. That file was &lt;code&gt;/etc/hosts&lt;/code&gt;. Today we have massive distributed DNS infrastructure, but this file still exists and still takes priority over DNS lookups on most systems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;   &lt;span class="n"&gt;localhost&lt;/span&gt;
&lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;   &lt;span class="n"&gt;mymachine&lt;/span&gt;
&lt;span class="m"&gt;192&lt;/span&gt;.&lt;span class="m"&gt;168&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;50&lt;/span&gt;  &lt;span class="n"&gt;dev&lt;/span&gt;-&lt;span class="n"&gt;server&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;ping dev-server.local&lt;/code&gt; after adding an entry there and it works instantly, no DNS query needed. Developers use this trick all the time to redirect domains locally during development. Security researchers use it to block known malicious domains by pointing them to &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The order in which Linux resolves names is controlled by another file: &lt;code&gt;/etc/nsswitch.conf&lt;/code&gt;. Look for the line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;: &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="n"&gt;dns&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"files" means check &lt;code&gt;/etc/hosts&lt;/code&gt; first. "dns" means then go to the DNS server. Change that order and you change how your entire system resolves hostnames. That one line has enormous consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  4: &lt;code&gt;/etc/resolv.conf&lt;/code&gt; Is Your Gateway to the Internet
&lt;/h2&gt;

&lt;p&gt;When &lt;code&gt;/etc/hosts&lt;/code&gt; doesn't have an answer, Linux goes to a DNS resolver. The configuration for which DNS server to use lives in &lt;code&gt;/etc/resolv.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;nameserver&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;.&lt;span class="m"&gt;8&lt;/span&gt;.&lt;span class="m"&gt;8&lt;/span&gt;.&lt;span class="m"&gt;8&lt;/span&gt;
&lt;span class="n"&gt;nameserver&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;search&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt;.&lt;span class="n"&gt;lan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple file, massive implications. The &lt;code&gt;nameserver&lt;/code&gt; lines tell the system where to send DNS queries. The &lt;code&gt;search&lt;/code&gt; line appends a domain suffix to short hostnames — so typing &lt;code&gt;ssh webserver&lt;/code&gt; might actually try &lt;code&gt;webserver.local.lan&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;On modern systems using &lt;code&gt;systemd-resolved&lt;/code&gt; or &lt;code&gt;NetworkManager&lt;/code&gt;, this file might actually be a symlink:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /etc/resolv.conf
&lt;span class="c"&gt;# → /etc/resolv.conf -&amp;gt; /run/systemd/resolve/stub-resolv.conf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means it's dynamically managed. The actual DNS logic lives elsewhere in systemd. The file is just a compatibility shim. I found this genuinely surprising — a file I assumed was simple and static is actually a managed symlink pointing to a runtime-generated config.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4qcq8h4qsa86ntoh635.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4qcq8h4qsa86ntoh635.png" alt="DNS resolution chain" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5: &lt;code&gt;/proc&lt;/code&gt; Is Not a Real Directory
&lt;/h2&gt;

&lt;p&gt;This one blew my mind a little. The &lt;code&gt;/proc&lt;/code&gt; directory looks like a folder full of files, but none of those files actually exist on your disk. &lt;code&gt;/proc&lt;/code&gt; is a virtual filesystem - it's generated on-the-fly by the Linux kernel every time you read from it.&lt;/p&gt;

&lt;p&gt;Run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cpuinfo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll get detailed info about your CPU: model name, cores, cache size, flags showing what features it supports. That data isn't stored anywhere. The kernel just synthesizes it when you ask.&lt;/p&gt;

&lt;p&gt;Same with &lt;code&gt;/proc/meminfo&lt;/code&gt; - it gives you a live breakdown of your RAM usage. &lt;code&gt;MemTotal&lt;/code&gt;, &lt;code&gt;MemFree&lt;/code&gt;, &lt;code&gt;Cached&lt;/code&gt;, &lt;code&gt;SwapUsed&lt;/code&gt;. Tools like &lt;code&gt;free -h&lt;/code&gt; and &lt;code&gt;htop&lt;/code&gt; are literally just reading this file and formatting the output nicely.&lt;/p&gt;

&lt;p&gt;The design philosophy here is beautiful: instead of a special system call API for every piece of system information, Linux just exposes everything as files. Want CPU info? Read a file. Want memory stats? Read a file. This makes it incredibly easy to access kernel internals from any language that can open a file.&lt;/p&gt;

&lt;h2&gt;
  
  
  6: &lt;code&gt;/proc/[PID]&lt;/code&gt; Lets You Inspect Any Running Process
&lt;/h2&gt;

&lt;p&gt;Every running process on your system has a numbered folder inside &lt;code&gt;/proc&lt;/code&gt;. Find your shell's PID with &lt;code&gt;echo $$&lt;/code&gt;, then explore that folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /proc/&lt;span class="nv"&gt;$$&lt;/span&gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cmdline&lt;/code&gt; — the exact command that launched this process&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;environ&lt;/code&gt; — all environment variables it was started with&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fd/&lt;/code&gt; — a folder containing symlinks to every open file descriptor&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maps&lt;/code&gt; — the memory map: which libraries and files are loaded into memory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt; — current state, memory usage, which user it runs as&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;fd/&lt;/code&gt; folder is particularly useful. Every file the process has open — log files, network sockets, config files — shows up here as a symlink. This is how tools like &lt;code&gt;lsof&lt;/code&gt; work. They're just walking through &lt;code&gt;/proc/[PID]/fd/&lt;/code&gt; for every process and reporting what they find.&lt;/p&gt;

&lt;p&gt;One practical insight: if a program deletes a file that another process still has open, the data isn't actually gone yet. It stays on disk until the file descriptor is closed. You can even recover the data by copying from &lt;code&gt;/proc/[PID]/fd/[number]&lt;/code&gt;. That's a useful recovery trick.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftx24mkngtfzvozogrzfj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftx24mkngtfzvozogrzfj.png" alt="/proc/[PID]" width="800" height="963"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7: &lt;code&gt;/proc/net/route&lt;/code&gt; Shows Your Routing Table as a File
&lt;/h2&gt;

&lt;p&gt;Most people use &lt;code&gt;ip route&lt;/code&gt; or &lt;code&gt;netstat -r&lt;/code&gt; to see the routing table. But those tools are just reading &lt;code&gt;/proc/net/route&lt;/code&gt;. Open the raw file and you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Iface  Destination  Gateway  Flags  RefCnt  Use  Metric  Mask  MTU  Window  IRTT
eth0   00000000     0101A8C0 0003   0       0    100     00000000 0  0       0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The values are in hex and reversed byte order, which looks confusing at first. But once you decode them: &lt;code&gt;0101A8C0&lt;/code&gt; reversed in pairs is &lt;code&gt;C0.A8.01.01&lt;/code&gt; which in decimal is &lt;code&gt;192.168.1.1&lt;/code&gt;. That's your default gateway.&lt;/p&gt;

&lt;p&gt;Flag &lt;code&gt;0003&lt;/code&gt; means the route is Up and is a Gateway route. This is the actual kernel routing table. Every packet your machine sends consults this structure to decide where to go next.&lt;/p&gt;

&lt;p&gt;Understanding this means you understand why changing your default gateway works the way it does, why VPNs can reroute all traffic (they add a more specific route), and why split tunneling is possible (some traffic goes through VPN, some doesn't, based on which route is more specific).&lt;/p&gt;

&lt;h2&gt;
  
  
  8: &lt;code&gt;/dev/null&lt;/code&gt;, &lt;code&gt;/dev/zero&lt;/code&gt;, and &lt;code&gt;/dev/urandom&lt;/code&gt; Are Not Real Files Either
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/dev&lt;/code&gt; directory is full of device files that let user space programs interact with hardware or kernel features. Most are physical device interfaces, but three stand out as being almost philosophical:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/dev/null&lt;/code&gt; is a black hole. Anything written to it disappears. Anything read from it returns nothing. You'll see it everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;command &lt;/span&gt;2&amp;gt;/dev/null   &lt;span class="c"&gt;# silence error output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/dev/zero&lt;/code&gt; is an infinite source of null bytes. Read from it and you get zeros forever. It's used to create empty files of a specific size, wipe disks, or pre-allocate space.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/dev/urandom&lt;/code&gt; is an infinite source of random bytes, generated by the kernel's entropy pool (keyboard timings, disk interrupts, network packets, etc.). It's how secure random numbers are generated on Linux. When your system generates an SSH key, it's reading from here. When your browser generates a TLS session key, it eventually traces back to here.&lt;/p&gt;

&lt;p&gt;The kernel trick of representing these as files means any program can use them without needing special APIs. A shell script can generate random data just as easily as a compiled C program.&lt;/p&gt;

&lt;h2&gt;
  
  
  9: &lt;code&gt;/etc/fstab&lt;/code&gt; Decides What Gets Mounted at Boot
&lt;/h2&gt;

&lt;p&gt;Every time Linux boots, it reads &lt;code&gt;/etc/fstab&lt;/code&gt; to figure out which filesystems to mount and where:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UUID=abc123   /         ext4  defaults        0 1
UUID=def456   /home     ext4  defaults        0 2
UUID=ghi789   swap      swap  sw              0 0
tmpfs         /tmp      tmpfs mode=1777,size=2G 0 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using UUIDs instead of device names like &lt;code&gt;/dev/sda1&lt;/code&gt; is a modern improvement. Device names can change if you add a new disk, but UUIDs are stable. Each field means: device, mount point, filesystem type, mount options, dump flag, fsck order.&lt;/p&gt;

&lt;p&gt;The last line is interesting: &lt;code&gt;tmpfs&lt;/code&gt; on &lt;code&gt;/tmp&lt;/code&gt; means your temp folder is actually stored in RAM, not on disk. Everything in &lt;code&gt;/tmp&lt;/code&gt; is gone on reboot and reads/writes happen at memory speed. Many systems don't do this by default, but it's a performance and privacy improvement when you add it.&lt;/p&gt;

&lt;p&gt;If you mess up &lt;code&gt;/etc/fstab&lt;/code&gt;, the system can fail to boot. It's one of those files that has enormous power with very little protection against mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  10: &lt;code&gt;/etc/systemd/system/&lt;/code&gt; Is Where Services Live
&lt;/h2&gt;

&lt;p&gt;On modern Linux systems using systemd, services are defined as unit files. The system ones live in &lt;code&gt;/lib/systemd/system/&lt;/code&gt;, but anything in &lt;code&gt;/etc/systemd/system/&lt;/code&gt; takes priority and overrides them. This is where you put custom services or modifications to existing ones.&lt;/p&gt;

&lt;p&gt;Open any &lt;code&gt;.service&lt;/code&gt; file, like &lt;code&gt;ssh.service&lt;/code&gt;, and you'll find it surprisingly readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;OpenBSD Secure Shell server&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target auditd.service&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/sbin/sshd -D&lt;/span&gt;
&lt;span class="py"&gt;ExecReload&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/bin/kill -HUP $MAINPID&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;After=&lt;/code&gt; directive is what makes the dependency graph work. Systemd reads all unit files at boot and builds a dependency tree, then starts services in parallel where possible, serializing only where &lt;code&gt;After=&lt;/code&gt; or &lt;code&gt;Requires=&lt;/code&gt; demands it. This is why modern Linux boots faster than older init systems.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Restart=on-failure&lt;/code&gt; is particularly useful. If your web server crashes, systemd just restarts it automatically. No daemon needed to babysit it.&lt;/p&gt;

&lt;h2&gt;
  
  
  11: &lt;code&gt;/var/log/auth.log&lt;/code&gt; Is a Security Timeline
&lt;/h2&gt;

&lt;p&gt;If you want to know what's been happening on your machine security-wise, read &lt;code&gt;/var/log/auth.log&lt;/code&gt; (or &lt;code&gt;/var/log/secure&lt;/code&gt; on Red Hat-based systems). Every login attempt, sudo usage, SSH connection, and authentication failure is logged here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo grep&lt;/span&gt; &lt;span class="s2"&gt;"Failed password"&lt;/span&gt; /var/log/auth.log | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $11}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command pulls out every failed password attempt, groups by IP address, and shows you who's been hammering your machine. On any internet-facing server, you'll see hundreds or thousands of attempts from random IPs. This is constant and automated — bots scanning the entire internet looking for weak passwords.&lt;/p&gt;

&lt;p&gt;Looking at the timestamps and patterns in this log teaches you more about real-world security threats than almost any textbook. You can see brute force patterns (same IP, rapid attempts), credential stuffing (many different usernames tried), and distributed attacks (same pattern from many IPs). Tools like &lt;code&gt;fail2ban&lt;/code&gt; read exactly this file and auto-block attacking IPs.&lt;/p&gt;

&lt;h2&gt;
  
  
  12: &lt;code&gt;/etc/sudoers&lt;/code&gt; Controls Who Can Do What as Root
&lt;/h2&gt;

&lt;p&gt;Running &lt;code&gt;sudo visudo&lt;/code&gt; (the safe way to edit this file) reveals the rules governing privilege escalation. The default you'll always see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;root&lt;/span&gt;    &lt;span class="n"&gt;ALL&lt;/span&gt;=(&lt;span class="n"&gt;ALL&lt;/span&gt;:&lt;span class="n"&gt;ALL&lt;/span&gt;) &lt;span class="n"&gt;ALL&lt;/span&gt;
%&lt;span class="n"&gt;sudo&lt;/span&gt;   &lt;span class="n"&gt;ALL&lt;/span&gt;=(&lt;span class="n"&gt;ALL&lt;/span&gt;:&lt;span class="n"&gt;ALL&lt;/span&gt;) &lt;span class="n"&gt;ALL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means: user &lt;code&gt;root&lt;/code&gt; can run all commands on all hosts as any user. Members of the &lt;code&gt;sudo&lt;/code&gt; group (the &lt;code&gt;%&lt;/code&gt; prefix means group) can do the same. But you can get much more specific:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;john&lt;/span&gt;    &lt;span class="n"&gt;ALL&lt;/span&gt;=(&lt;span class="n"&gt;ALL&lt;/span&gt;) &lt;span class="n"&gt;NOPASSWD&lt;/span&gt;: /&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;systemctl&lt;/span&gt; &lt;span class="n"&gt;restart&lt;/span&gt; &lt;span class="n"&gt;nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets &lt;code&gt;john&lt;/code&gt; restart nginx without a password, but nothing else. This principle of least privilege is a core security concept — give people only the permissions they actually need. If john's account gets compromised, the attacker can restart nginx but can't do much else.&lt;/p&gt;

&lt;p&gt;The file uses a strict syntax, and if you corrupt it, you can lock yourself out of sudo entirely. That's why &lt;code&gt;visudo&lt;/code&gt; exists — it validates syntax before saving. It's one of those files where Linux forces you to use a specific editor to protect you from yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;What surprised me most wasn't any single file. It was the consistent philosophy running through all of it. Linux treats everything as a file. Hardware, processes, random number generators, network state, running services. This "everything is a file" design means that the same tools (reading, writing, piping, permissions) work across completely different concerns. A web developer reading a text file and a kernel reading a network interface are using the same fundamental operation.&lt;/p&gt;

&lt;p&gt;That's why Linux is so composable. You can pipe &lt;code&gt;/dev/urandom&lt;/code&gt; through a text processor, read live CPU stats with a simple &lt;code&gt;cat&lt;/code&gt;, redirect a program's output into nothing using a file called &lt;code&gt;null&lt;/code&gt;. The whole system is built on one unifying abstraction, and once that clicks, Linux stops feeling like a collection of magic commands and starts feeling like a coherent, logical system you can actually reason about.&lt;/p&gt;

&lt;p&gt;Go hunt around in these directories yourself. You'll find things that aren't in this post. That's kind of the whole point.&lt;/p&gt;




&lt;p&gt;Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, where I post more about web development.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>filesystem</category>
    </item>
    <item>
      <title>How React's Virtual DOM Works Under the Hood</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Thu, 28 May 2026 06:55:27 +0000</pubDate>
      <link>https://dev.to/satyasootar/how-reacts-virtual-dom-works-under-the-hood-1m30</link>
      <guid>https://dev.to/satyasootar/how-reacts-virtual-dom-works-under-the-hood-1m30</guid>
      <description>&lt;p&gt;So you've been using React for a while, calling &lt;code&gt;setState&lt;/code&gt;, watching your UI update magically, and somewhere in the back of your head you've been wondering... how does this actually work? Like, what is React actually doing between the moment you change some state and the moment your screen updates?&lt;/p&gt;

&lt;p&gt;That's exactly what we're going to break down today. No fluff, no theory marathons. Just a clear, step-by-step walkthrough of what's really happening under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, Let's Talk About the Real Problem
&lt;/h2&gt;

&lt;p&gt;Before Virtual DOM even makes sense, you need to understand what problem it's solving.&lt;/p&gt;

&lt;p&gt;The browser has something called the &lt;strong&gt;Real DOM&lt;/strong&gt; (Document Object Model). Think of it as a giant tree of objects that represents every element on your page. Every &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, every &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, every &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tag lives in this tree. When you update something, the browser has to repaint, reflow, and recalculate layouts. And doing that frequently? It's expensive. To understand how a browser works, check out &lt;a href="https://dev.to/satyasootar/how-a-browser-works-a-beginner-friendly-guide-to-browser-internals-4opn"&gt;this&lt;/a&gt; blog.&lt;/p&gt;

&lt;p&gt;Here's the thing about direct DOM manipulation: it's not the reading that's slow, it's the writing. Every time you touch the DOM, the browser kicks off a cascade of work. If you're updating ten different elements separately, you're triggering that cascade ten times. In older jQuery-style apps, developers would do things like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-age&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;25&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-avatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;john.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...and so on for every little thing that changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each one of those is a direct punch to the Real DOM. And as your app grows, those punches add up fast. The UI stutters, animations drop frames, and users start noticing.&lt;/p&gt;

&lt;p&gt;React's answer to this was: what if we stop going directly to the DOM every single time something changes?&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter the Virtual DOM
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Virtual DOM&lt;/strong&gt; is essentially a lightweight JavaScript object that mirrors the structure of the Real DOM. It's not a browser thing, it's just plain JavaScript living in memory.&lt;/p&gt;

&lt;p&gt;When React renders your components, it doesn't immediately write to the Real DOM. Instead, it first builds this Virtual DOM tree, a nested JavaScript object that describes what the UI should look like. Something like this internally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, John&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Frontend Developer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is basically what your JSX compiles down to. That &lt;code&gt;&amp;lt;div className="container"&amp;gt;&lt;/code&gt; you write? React transforms it into something like the object above using &lt;code&gt;React.createElement()&lt;/code&gt; calls behind the scenes.&lt;/p&gt;

&lt;p&gt;Creating and comparing JavaScript objects is super cheap compared to touching the browser's DOM. That's the whole game here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9rxsxtjw8jne5x0j8m2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9rxsxtjw8jne5x0j8m2.png" alt="flow" width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Initial Render: How It All Starts
&lt;/h2&gt;

&lt;p&gt;When your React app loads for the first time, here's exactly what happens:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; React takes your root component (usually &lt;code&gt;&amp;lt;App /&amp;gt;&lt;/code&gt;) and starts calling your component functions one by one, top to bottom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Each component returns JSX, which gets compiled into &lt;code&gt;React.createElement()&lt;/code&gt; calls, building up that Virtual DOM tree we talked about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Once the full Virtual DOM tree is ready, React takes it and does a single, efficient pass to build the Real DOM and inject it into your &lt;code&gt;&amp;lt;div id="root"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This first render is actually doing real DOM work, React has no choice here because the page is blank. But it does it in one shot, building everything in memory first and then committing it to the browser in one go. This is already smarter than doing a bunch of individual DOM manipulations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now Something Changes: State or Props Update
&lt;/h2&gt;

&lt;p&gt;This is where the magic really happens. Let's say a user clicks a button and you call &lt;code&gt;setState&lt;/code&gt;. Here's the sequence of events React kicks off:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; React marks the component as "needs re-render."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; React calls your component function again and produces a brand new Virtual DOM tree. This is a fresh snapshot of what the UI should look like after the change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; React now has two Virtual DOM trees sitting in memory: the &lt;strong&gt;old tree&lt;/strong&gt; (what the UI currently looks like) and the &lt;strong&gt;new tree&lt;/strong&gt; (what it should look like after the update).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; React runs a process called &lt;strong&gt;diffing&lt;/strong&gt;, comparing the old tree to the new tree to figure out exactly what changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; React collects all the differences into a minimal set of changes, called a &lt;strong&gt;patch&lt;/strong&gt;, and applies only those changes to the Real DOM.&lt;/p&gt;

&lt;p&gt;That last part is the key insight. Instead of re-rendering everything, React surgically updates only what actually changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzd0tob6y7vokg9rnpsjg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzd0tob6y7vokg9rnpsjg.png" alt="flow 2" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Diffing: How React Compares Two Trees
&lt;/h2&gt;

&lt;p&gt;Diffing is the process of comparing the old Virtual DOM tree with the new one. The technical term React uses is &lt;strong&gt;reconciliation&lt;/strong&gt;. Let's make this concrete.&lt;/p&gt;

&lt;p&gt;Imagine your old tree looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ul&amp;gt;
  &amp;lt;li&amp;gt;Apple&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;Banana&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;Cherry&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after a state change, the new tree looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ul&amp;gt;
  &amp;lt;li&amp;gt;Apple&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;Mango&amp;lt;/li&amp;gt;    &amp;lt;-- changed
  &amp;lt;li&amp;gt;Cherry&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React walks through both trees simultaneously, node by node. When it hits &lt;code&gt;&amp;lt;li&amp;gt;Banana&amp;lt;/li&amp;gt;&lt;/code&gt; in the old tree and &lt;code&gt;&amp;lt;li&amp;gt;Mango&amp;lt;/li&amp;gt;&lt;/code&gt; in the new tree, it flags that as a change. Everything else matches, so React only updates that one text node in the Real DOM. One update. Not three.&lt;/p&gt;

&lt;p&gt;React's diffing uses two important heuristics to stay fast:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Elements of different types produce completely different trees.&lt;/strong&gt; If you switch a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; to a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;, React doesn't try to figure out what to reuse. It just tears down the old subtree and builds a new one from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The &lt;code&gt;key&lt;/code&gt; prop helps React track list items.&lt;/strong&gt; This is why React warns you when you render a list without keys. Without keys, if you insert an item at the top of a list, React thinks every single item changed because positions shifted. With keys, React can figure out "oh, this item just moved, it didn't change" and skip unnecessary updates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Without keys: React gets confused on reorder/insert&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)}&lt;/span&gt;

&lt;span class="c1"&gt;// With keys: React tracks each item correctly&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrt6t1ym089i7ww8tt8j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrt6t1ym089i7ww8tt8j.png" alt="Flow 3" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Minimal Updates: Updating Only What Changed
&lt;/h2&gt;

&lt;p&gt;So React has figured out the diff. Now what?&lt;/p&gt;

&lt;p&gt;React batches all those changes into what's called a &lt;strong&gt;commit phase&lt;/strong&gt;. Instead of applying each change one at a time (which would trigger multiple browser repaints), React collects all necessary DOM operations and applies them together in a single pass.&lt;/p&gt;

&lt;p&gt;This is huge for performance. The browser gets to do its expensive reflow/repaint work exactly once, no matter how many things changed in your component tree.&lt;/p&gt;

&lt;p&gt;Let's think about this with an example. Say you have a dashboard with 50 components. The user updates their profile name. Without Virtual DOM, you might naively re-render every component touching the DOM 50 times. With React's approach, React re-renders the components in memory (cheap, it's just JavaScript), figures out only the name text node changed, and makes exactly one targeted update to the Real DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Actually Improves Performance
&lt;/h2&gt;

&lt;p&gt;Let's put it all together with a clear picture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct DOM manipulation&lt;/strong&gt; forces you to write individual DOM operations manually. If you're not careful, you'll trigger multiple reflows and repaints. At scale, this becomes a performance nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React's Virtual DOM approach&lt;/strong&gt; gives you a much smarter pipeline. You describe what your UI should look like, React figures out how to get there with the fewest possible DOM operations, and the browser has to do the least amount of expensive work.&lt;/p&gt;

&lt;p&gt;It's like the difference between giving someone directions one turn at a time ("turn left, now turn right, now go straight...") versus handing them a full route upfront so they can optimize the path themselves.&lt;/p&gt;

&lt;p&gt;There's also a developer experience benefit here. You write declarative code ("here's what the UI should look like") instead of imperative code ("do this, then that, then this"). React handles the imperative DOM manipulation internally so you don't have to think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full React Update Lifecycle
&lt;/h2&gt;

&lt;p&gt;Let's put the entire thing together in one clean mental model. Every update in React goes through three phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render Phase&lt;/strong&gt; - React calls your component functions and builds the new Virtual DOM tree. This is pure JavaScript computation, nothing touches the browser yet. React can pause, abort, or restart this phase if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diff Phase&lt;/strong&gt; - React compares the new Virtual DOM tree against the previous one (reconciliation). It walks the two trees and builds a list of changes. Still no Real DOM involvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commit Phase&lt;/strong&gt; - React takes the list of changes and applies them to the Real DOM all at once. This is the only point where the browser gets involved. After this, effects like &lt;code&gt;useEffect&lt;/code&gt; run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzrarvd3tb0gogwoerk7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzrarvd3tb0gogwoerk7.png" alt="Three phases of react" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;Here's the simplest way to think about it. The Real DOM is like a physical whiteboard in an office. Erasing and redrawing stuff on it takes time and effort. The Virtual DOM is like a personal notebook. Writing in your notebook is fast, comparing two pages in your notebook is fast. Only once you know exactly what needs to change do you walk up to the whiteboard and make the minimum number of edits.&lt;/p&gt;

&lt;p&gt;React is basically saying: let me do all the thinking in my notebook first, and I'll only bother the whiteboard when I know exactly what to write.&lt;/p&gt;

&lt;p&gt;That's the entire mental model. Your components describe what the UI should look like. React keeps a copy of that description in memory as the Virtual DOM. When something changes, React builds a new description, compares it to the old one, finds the delta, and applies only that delta to the Real DOM. Fast, efficient, and you never have to think about DOM manipulation manually.&lt;/p&gt;

&lt;p&gt;Once you internalize this flow, a lot of React's other behaviors start making more sense too. Why batching state updates matters. Why keys in lists are important. Why &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;React.memo&lt;/code&gt; exist. They're all tools that work with or optimize different parts of this same pipeline.&lt;/p&gt;




&lt;p&gt;Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, where I post more about web development.&lt;/p&gt;

</description>
      <category>react</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Sessions vs JWT vs Cookies: Understanding Authentication Approaches</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Sun, 10 May 2026 14:15:37 +0000</pubDate>
      <link>https://dev.to/satyasootar/sessions-vs-jwt-vs-cookies-understanding-authentication-approaches-1ojo</link>
      <guid>https://dev.to/satyasootar/sessions-vs-jwt-vs-cookies-understanding-authentication-approaches-1ojo</guid>
      <description>&lt;p&gt;Hello readers 👋, welcome to the 15th blog in our Node.js series!&lt;/p&gt;

&lt;p&gt;In our previous posts, we built a REST API, learned how to protect routes with JWT, and explored middleware and file uploads. Authentication has come up several times, but today we’re going to take a step back and look at the bigger picture. We’ll compare three pillars of authentication in web applications: &lt;strong&gt;sessions&lt;/strong&gt;, &lt;strong&gt;JSON Web Tokens (JWT)&lt;/strong&gt;, and &lt;strong&gt;cookies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you’ve ever been confused about when to use a session-based login, when to reach for a JWT, or where cookies fit into all this, this post will clear things up. We’ll keep it practical, avoid deep security rabbit holes, and end with a decision framework you can apply to your next project.&lt;/p&gt;

&lt;p&gt;Let’s get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are cookies?
&lt;/h2&gt;

&lt;p&gt;Cookies are small pieces of data stored on the client (browser) by the server via the &lt;code&gt;Set-Cookie&lt;/code&gt; header. They are automatically sent back to the server with every subsequent request to the same domain. That’s their superpower: they travel with every request without the client having to do anything extra.&lt;/p&gt;

&lt;p&gt;A cookie looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser then dutifully sends &lt;code&gt;Cookie: sessionId=abc123&lt;/code&gt; with each request.&lt;/p&gt;

&lt;p&gt;Cookies alone are not an authentication method; they are a &lt;strong&gt;transport mechanism&lt;/strong&gt;. They can carry a session ID, a JWT, or just a simple preference. But because sessions almost always rely on cookies, the two are often mentioned together.&lt;/p&gt;

&lt;p&gt;Key properties you can set on cookies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HttpOnly&lt;/strong&gt; – the cookie cannot be accessed via JavaScript (&lt;code&gt;document.cookie&lt;/code&gt;), which helps prevent XSS attacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure&lt;/strong&gt; – the cookie is only sent over HTTPS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SameSite&lt;/strong&gt; – controls whether the cookie is sent on cross‑site requests, mitigating CSRF.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a traditional session‑based authentication, the server sets a cookie containing a unique session ID, and all further requests carry that cookie to identify the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are sessions?
&lt;/h2&gt;

&lt;p&gt;Session‑based authentication is the classic approach. After the user logs in with valid credentials, the server creates a &lt;strong&gt;session&lt;/strong&gt; – a record in a database, in‑memory store, or cache (like Redis) that contains the user’s identity and any other relevant data. The server then sends back a cookie holding only the session ID.&lt;/p&gt;

&lt;p&gt;Every subsequent request comes with that cookie. The server reads the session ID, looks up the session data (often from a store), and knows who the user is without them sending the password again.&lt;/p&gt;

&lt;p&gt;Let’s see a simplified Express example using &lt;code&gt;express-session&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express-session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-secret-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;resave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;saveUninitialized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;httpOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// authenticate user&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// stored on server&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Logged in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// fetch user data using session.userId&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Hello user &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sessions are &lt;strong&gt;stateful&lt;/strong&gt;: the server must maintain a session store (memory, database, Redis). When you scale to multiple server instances, the session store must be shared, which adds complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are JWT tokens?
&lt;/h2&gt;

&lt;p&gt;JSON Web Tokens (JWT) are a &lt;strong&gt;stateless&lt;/strong&gt; authentication method. After a successful login, the server generates a token containing a payload (like the user’s ID and role) and signs it with a secret key. The token is sent to the client, which stores it. For each subsequent request, the client sends the token, usually in the &lt;code&gt;Authorization&lt;/code&gt; header as a Bearer token, or sometimes in an HttpOnly cookie. The server verifies the signature, extracts the payload, and trusts that the user is who the token claims.&lt;/p&gt;

&lt;p&gt;We’ve already implemented JWT authentication in an earlier blog. Here’s a quick recap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// authenticate user&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1h&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Profile of user &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because JWT contains all the info needed (in the payload), the server doesn’t need to look up a session. That’s the stateless magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stateful vs stateless authentication
&lt;/h2&gt;

&lt;p&gt;This is the key distinction.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stateful (sessions)&lt;/strong&gt;: The server keeps track of authenticated users. Each request requires a session lookup. This works great for traditional multi‑page apps because scaling requires a shared session store. Logout is easy: just destroy the session on the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateless (JWT)&lt;/strong&gt;: The server doesn’t store anything. It trusts the token after verifying the signature. This makes horizontal scaling a breeze. Logout is trickier: since the server doesn’t remember the token, you can’t invalidate it without a blacklist (bringing back some state). Usually, you set short expiration and rely on the client removing the token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of sessions as a coat-check ticket: you give the clerk your coat, get a ticket, and later hand the ticket back to retrieve your coat. The clerk (server) remembers you. JWT is like a digital passport: the passport says who you are, and the border official (server) verifies its authenticity without calling home. Both have their place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sessions vs JWT: detailed comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Session‑based&lt;/th&gt;
&lt;th&gt;JWT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stateful (server stores session data)&lt;/td&gt;
&lt;td&gt;Stateless (server only verifies token)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Session ID stored in a cookie; session data stored server‑side (memory, DB, Redis)&lt;/td&gt;
&lt;td&gt;Token stored client‑side: browser (localStorage, sessionStorage) or HttpOnly cookie; no server‑side storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires shared session store across instances (e.g., Redis)&lt;/td&gt;
&lt;td&gt;Easy to scale horizontally; no server‑side data sharing needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Logout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server deletes session – immediate effect&lt;/td&gt;
&lt;td&gt;No server‑side record; token can’t be invalidated instantly unless a blacklist is used. Short expiry &amp;amp; client removal are typical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security typical risks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Session hijacking (if cookie stolen), CSRF (mitigated with SameSite, tokens)&lt;/td&gt;
&lt;td&gt;Token theft (if stored in non‑HttpOnly cookie or localStorage), XSS (if accessible via JS), token size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payload visibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server‑side, opaque to client&lt;/td&gt;
&lt;td&gt;Payload is Base64‑encoded (not encrypted); everyone can read it, but signature ensures integrity. Never put secrets in payload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typical use case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Traditional server‑rendered web apps, multi‑page apps, apps with centralized user management&lt;/td&gt;
&lt;td&gt;APIs, single‑page applications (SPAs), mobile apps, microservices, third‑party integrations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note that you &lt;em&gt;can&lt;/em&gt; store a JWT inside an HttpOnly cookie, blending some benefits: you get automatic cookie sending (no need to attach header manually) and protection from XSS, while still being stateless. The choice of where to store the token is separate from the choice of session vs JWT.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use each method
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use sessions when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have a traditional server‑rendered application (EJS, Pug, etc.).&lt;/li&gt;
&lt;li&gt;You need fine‑grained control over active session invalidation (e.g., force logout, revoke access instantly).&lt;/li&gt;
&lt;li&gt;You’re comfortable managing a session store (or don’t mind the overhead for a simple app).&lt;/li&gt;
&lt;li&gt;You want to keep sensitive data on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use JWT when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are building a REST API that serves multiple frontends (web, mobile, desktop).&lt;/li&gt;
&lt;li&gt;You want to scale horizontally without worrying about a shared data store.&lt;/li&gt;
&lt;li&gt;You need to pass authorization across multiple independent services (microservices).&lt;/li&gt;
&lt;li&gt;Your client is a SPA where the frontend handles presentation and stores the token (e.g., in memory or localStorage, though HttpOnly cookie is safer).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A hybrid approach&lt;/strong&gt;: store a short‑lived JWT inside an HttpOnly, Secure, SameSite cookie. That way you get automatic cookie handling, XSS protection, and still remain stateless on the server. This is increasingly popular in modern frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Sessions, cookies, and JWT aren’t mutually exclusive; they’re pieces of the authentication puzzle. Cookies transport data (session IDs or JWTs) seamlessly. Sessions give you server‑side control at the cost of state. JWT gives you stateless scalability at the cost of instant invalidation. Choosing between them depends on your application architecture, scalability needs, and security priorities.&lt;/p&gt;

&lt;p&gt;To recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cookies&lt;/strong&gt; are a transport mechanism, automatically sent with every request, and can be secured with HttpOnly, Secure, and SameSite flags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sessions&lt;/strong&gt; store user state on the server and use a cookie holding a session ID. They are stateful and great for traditional web apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWT&lt;/strong&gt; embeds user information in a signed token; the server doesn’t need a lookup. They are stateless and ideal for APIs, SPAs, and microservices.&lt;/li&gt;
&lt;li&gt;The decision often hinges on stateful vs stateless needs and where you want to store sensitive data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next post, we’ll start connecting Express to databases; building a full‑stack authentication flow with registration and login that persists real data. See you then!&lt;/p&gt;




&lt;p&gt;Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, where I post more about web development.&lt;/p&gt;

</description>
      <category>jwt</category>
      <category>cookies</category>
      <category>node</category>
    </item>
    <item>
      <title>URL Parameters vs Query Strings in Express.js</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Sun, 10 May 2026 14:09:37 +0000</pubDate>
      <link>https://dev.to/satyasootar/url-parameters-vs-query-strings-in-expressjs-154o</link>
      <guid>https://dev.to/satyasootar/url-parameters-vs-query-strings-in-expressjs-154o</guid>
      <description>&lt;p&gt;Hello readers 👋, welcome to the 14th blog in our Node.js series!&lt;/p&gt;

&lt;p&gt;In the last few posts, we built a complete file upload and serving system using Multer and Express. We learned how to accept files, store them safely, and serve them back via static URLs. Today, we're going to shift focus to something more subtle but equally important: how to extract information from the URL itself. Specifically, we'll talk about &lt;strong&gt;URL parameters&lt;/strong&gt; and &lt;strong&gt;query strings&lt;/strong&gt; in Express.js.&lt;/p&gt;

&lt;p&gt;You see these every day: URLs like &lt;code&gt;/users/42&lt;/code&gt; or &lt;code&gt;/search?q=express&amp;amp;sort=asc&lt;/code&gt;. Knowing when to use a parameter (the &lt;code&gt;42&lt;/code&gt;) versus a query string (the &lt;code&gt;?q=express&lt;/code&gt;) makes your API design cleaner and more intuitive. Let's break it all down with clear examples.&lt;/p&gt;

&lt;p&gt;Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  What URL parameters are
&lt;/h2&gt;

&lt;p&gt;URL parameters (also called route parameters) are segments of the URL path that act as placeholders. They are part of the path itself and are typically used to identify a specific resource. In Express, you define them with a colon (&lt;code&gt;:&lt;/code&gt;) prefix in the route path, and Express captures their values into &lt;code&gt;req.params&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Think of &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; as a placeholder for something like an ID, a username, or a category. The actual value appears in the URL after the previous segment.&lt;/p&gt;

&lt;p&gt;Here's a basic route that uses a parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/:userId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User profile for ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you visit &lt;code&gt;http://localhost:3000/users/123&lt;/code&gt;, &lt;code&gt;req.params.userId&lt;/code&gt; will be &lt;code&gt;"123"&lt;/code&gt;. Notice that the parameter is part of the path. It's not optional; the route wouldn't match &lt;code&gt;/users/&lt;/code&gt; alone (without an ID).&lt;/p&gt;

&lt;p&gt;You can have multiple parameters in a single route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts/:postId/comments/:commentId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;commentId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;commentId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The values are always strings, so you may need to parse them into numbers if needed (&lt;code&gt;parseInt&lt;/code&gt;). Express doesn't do any type conversion automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What query strings (query parameters) are
&lt;/h2&gt;

&lt;p&gt;Query strings are key-value pairs that come after a question mark (&lt;code&gt;?&lt;/code&gt;) in a URL. They are used to send additional data that is often optional, like filters, sorting options, pagination, or search terms. They are not part of the route path itself. In Express, you access them via &lt;code&gt;req.query&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, a search URL might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/search?q=express&amp;amp;page=2&amp;amp;limit=10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;q&lt;/code&gt;, &lt;code&gt;page&lt;/code&gt;, and &lt;code&gt;limit&lt;/code&gt; are query parameters. Express parses them and makes them available in &lt;code&gt;req.query&lt;/code&gt; as an object.&lt;/p&gt;

&lt;p&gt;Example route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A request to &lt;code&gt;/search?q=node&amp;amp;page=3&lt;/code&gt; gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Query strings are extremely flexible because they don't affect route matching directly; any number of them can be added to a URL without breaking the route pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Differences between URL parameters and query strings
&lt;/h2&gt;

&lt;p&gt;The core difference lies in their &lt;strong&gt;purpose&lt;/strong&gt; and &lt;strong&gt;placement&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;URL Parameters (Route params)&lt;/th&gt;
&lt;th&gt;Query Strings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Appearance in URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Part of the path: &lt;code&gt;/users/42&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;After &lt;code&gt;?&lt;/code&gt; in the URL: &lt;code&gt;/users?role=admin&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Identify a specific resource (required)&lt;/td&gt;
&lt;td&gt;Provide additional instructions or filters (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Route matching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Must be defined in route path with &lt;code&gt;:param&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Does not affect which route is matched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Express access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;req.params&lt;/code&gt; (object)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;req.query&lt;/code&gt; (object)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typical use cases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Entity ID, username, category, blog post slug&lt;/td&gt;
&lt;td&gt;Search, pagination, sorting, filtering, API keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Order in URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fixed position in the path&lt;/td&gt;
&lt;td&gt;Unordered, can be combined arbitrarily&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Optionality&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usually required for the resource to make sense&lt;/td&gt;
&lt;td&gt;Often optional with defaults&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A good mental model: &lt;strong&gt;parameters point to a specific "noun" (the resource)&lt;/strong&gt;; &lt;strong&gt;query strings describe "how" or "what to do" with that resource&lt;/strong&gt;, like filtering or sorting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessing params in Express
&lt;/h2&gt;

&lt;p&gt;As shown earlier, you define placeholders in the route string using &lt;code&gt;:paramName&lt;/code&gt;. Then inside the handler, you read &lt;code&gt;req.params&lt;/code&gt;. All values are strings.&lt;/p&gt;

&lt;p&gt;If you need to make a parameter optional and still match the base route, you can use a &lt;code&gt;?&lt;/code&gt; after the parameter name (like &lt;code&gt;:userId?&lt;/code&gt;), but then you'd need to handle the case where it's undefined. Alternatively, define two routes: one with and one without the parameter.&lt;/p&gt;

&lt;p&gt;Here's an example of an optional parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/articles/:year?/:month?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Articles &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;for &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;month&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But be careful, &lt;code&gt;req.params&lt;/code&gt; will contain strings for whatever segments exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessing query strings in Express
&lt;/h2&gt;

&lt;p&gt;Query strings don't need to be declared. Any &lt;code&gt;key=value&lt;/code&gt; pair after the &lt;code&gt;?&lt;/code&gt; will appear in &lt;code&gt;req.query&lt;/code&gt;. If a key appears multiple times (e.g., &lt;code&gt;?tag=js&amp;amp;tag=node&lt;/code&gt;), Express by default gives you an array (only if you use a certain parsing library; the built-in &lt;code&gt;qs&lt;/code&gt; library parses arrays with repeated keys). So &lt;code&gt;req.query.tag&lt;/code&gt; might be &lt;code&gt;['js', 'node']&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example with arrays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// tags could be a string or an array if multiple&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tagsArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tagsArray&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to use params vs query: practical examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  User profile ID (use params)
&lt;/h3&gt;

&lt;p&gt;You want to get a specific user by ID. The ID is the identifier of the resource, so it belongs in the path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the URL clean: &lt;code&gt;/users/5&lt;/code&gt;. Without the ID, the route doesn't make sense. So a parameter is the right choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search filters (use query strings)
&lt;/h3&gt;

&lt;p&gt;You want to filter a list of products by category, price range, and sort order. None of these are "the resource identifier". They are optional, descriptive modifiers. So they go in the query string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;minPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sort&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minPrice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minPrice&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A request URL like &lt;code&gt;/products?category=electronics&amp;amp;minPrice=100&amp;amp;sort=asc&lt;/code&gt; clearly communicates that we're viewing the products collection but with specific filters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mixed usage
&lt;/h3&gt;

&lt;p&gt;Often, you'll combine both. For example, fetching comments for a specific post, with pagination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /posts/:postId/comments?page=2&amp;amp;limit=10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;:postId&lt;/code&gt; identifies the post (required), while &lt;code&gt;page&lt;/code&gt; and &lt;code&gt;limit&lt;/code&gt; control how many comments are returned (optional with defaults).&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualizing the URL structure
&lt;/h2&gt;

&lt;p&gt;Every URL can be broken down into parts. Let's disassemble an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.example.com/users/42?include=posts&amp;amp;limit=5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protocol &amp;amp; domain&lt;/strong&gt;: &lt;code&gt;https://api.example.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path&lt;/strong&gt;: &lt;code&gt;/users/42&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Segment &lt;code&gt;users&lt;/code&gt; (static)&lt;/li&gt;
&lt;li&gt;Segment &lt;code&gt;42&lt;/code&gt; (dynamic, captured by &lt;code&gt;:userId&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Query string&lt;/strong&gt;: &lt;code&gt;?include=posts&amp;amp;limit=5&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;include=posts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;limit=5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Express routes match against the path (ignoring the query string). So you'd define:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/:userId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then inside, &lt;code&gt;req.params.userId&lt;/code&gt; gives &lt;code&gt;"42"&lt;/code&gt;, &lt;code&gt;req.query.include&lt;/code&gt; gives &lt;code&gt;"posts"&lt;/code&gt;, and &lt;code&gt;req.query.limit&lt;/code&gt; gives &lt;code&gt;"5"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This separation allows the same route to respond differently based on what the client asks for, without defining countless different URL patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Understanding URL parameters versus query strings is essential for designing clean, predictable REST APIs. URL parameters are used to identify a specific resource (like a user or a post) and appear as dynamic segments in the path. Query strings are used to modify the request, provide filters, pagination, or any optional data, and appear after the &lt;code&gt;?&lt;/code&gt; in the URL. Express makes both accessible with &lt;code&gt;req.params&lt;/code&gt; and &lt;code&gt;req.query&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's quickly recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;URL parameters&lt;/strong&gt; (&lt;code&gt;:param&lt;/code&gt;) are parts of the path used for required resource identifiers. Accessed via &lt;code&gt;req.params&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query strings&lt;/strong&gt; (&lt;code&gt;?key=value&lt;/code&gt;) are optional key‑value pairs used for filtering, sorting, searching, and other modifiers. Accessed via &lt;code&gt;req.query&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Route matching ignores query strings, so they are flexible and don't affect which handler runs.&lt;/li&gt;
&lt;li&gt;Use parameters when the URL would be incomplete without that piece of data; use query strings for everything else that's optional or descriptive.&lt;/li&gt;
&lt;li&gt;In Express, both are available as simple JavaScript objects, making it easy to extract and use them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Armed with this understanding, your routes will become more intuitive and your APIs more predictable. See you in the next post!&lt;/p&gt;




&lt;p&gt;Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, where I post more about web development.&lt;/p&gt;

</description>
      <category>express</category>
      <category>node</category>
      <category>url</category>
      <category>query</category>
    </item>
    <item>
      <title>Storing Uploaded Files and Serving Them in Express</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Sun, 10 May 2026 14:05:18 +0000</pubDate>
      <link>https://dev.to/satyasootar/storing-uploaded-files-and-serving-them-in-express-ob4</link>
      <guid>https://dev.to/satyasootar/storing-uploaded-files-and-serving-them-in-express-ob4</guid>
      <description>&lt;p&gt;Hello readers 👋, welcome to the 13th blog in our Node.js series!&lt;/p&gt;

&lt;p&gt;In the last post, we learned how Multer helps us accept file uploads in Express, whether single files or multiple files. We configured a disk storage engine and saw how &lt;code&gt;req.file&lt;/code&gt; and &lt;code&gt;req.files&lt;/code&gt; give us all the details about what arrived. But one question remained unanswered: &lt;strong&gt;where do the uploaded files actually live, and how do clients get them back?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today we're going to close that loop. We'll talk about storage choices, how to serve uploaded files so they're accessible via a URL, and crucial security practices that keep your application safe. By the end, you'll have a complete picture of the upload‑serve lifecycle.&lt;/p&gt;

&lt;p&gt;Let's jump in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where uploaded files are stored
&lt;/h2&gt;

&lt;p&gt;When Multer saves a file on the server, it places it into a folder that you specify via the &lt;code&gt;destination&lt;/code&gt; callback. In our previous example, we used an &lt;code&gt;uploads&lt;/code&gt; folder at the root of our project. After a few uploads, the folder might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project/
├── uploads/
│   ├── avatar-1680000000-123456789.png
│   ├── avatar-1680000010-987654321.jpg
│   └── cover-1680000020-456789123.jpeg
├── server.js
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual filenames are generated by the &lt;code&gt;filename&lt;/code&gt; callback (often using a timestamp and a random number to avoid collisions). The original name is still available inside &lt;code&gt;req.file.originalname&lt;/code&gt;, but on disk we keep a sanitized, unique name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local storage vs external storage concept
&lt;/h2&gt;

&lt;p&gt;In development and small applications, storing files on the local filesystem is perfectly fine. It's simple and needs no extra setup. But as your application grows, you'll often hear about external storage services like Amazon S3, Google Cloud Storage, or Cloudinary. These services store files separately from your Node.js server, which brings several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your server's disk space isn't used by user files.&lt;/li&gt;
&lt;li&gt;The files can be served to clients directly from a CDN, reducing load on your application server.&lt;/li&gt;
&lt;li&gt;Scaling becomes easier because the storage layer is independent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this post, we'll focus entirely on local storage because that's where most people start. We'll mention how to serve those local files directly to clients. Later, when you move to cloud storage, the ideas of URLs and security transfer easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serving static files in Express
&lt;/h2&gt;

&lt;p&gt;After a file is uploaded and stored on disk, you typically want to make it accessible through a URL so that a client (browser, mobile app) can download or display it. Express provides a built‑in middleware called &lt;code&gt;express.static&lt;/code&gt; exactly for this purpose.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;express.static&lt;/code&gt; takes a folder path and serves all files inside it as static assets. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Serve files from the 'uploads' folder at the '/files' URL path&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if there is a file &lt;code&gt;uploads/avatar-123456.png&lt;/code&gt;, it becomes available at &lt;code&gt;http://localhost:3000/files/avatar-123456.png&lt;/code&gt;. The URL path you choose (&lt;code&gt;/files&lt;/code&gt;) can be anything; it doesn't have to match the folder name.&lt;/p&gt;

&lt;p&gt;A common practice is to serve the uploads folder under a dedicated path like &lt;code&gt;/uploads&lt;/code&gt; or &lt;code&gt;/files&lt;/code&gt; to keep the URL structure clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the complete flow
&lt;/h2&gt;

&lt;p&gt;Let's combine Multer and static serving into a single Express app to see the full picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;multer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Ensure uploads folder exists&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uploadsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadsDir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadsDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Multer configuration&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diskStorage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploads/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uniqueSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;E9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;uniqueSuffix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Serve uploaded files statically&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Upload route&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;photo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No file uploaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Build the public URL&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/files/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;File uploaded successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fileUrl&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After uploading, the client receives a JSON response containing the direct URL. It can then use that URL to display an image, download a document, or simply check the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessing uploaded files via URL
&lt;/h2&gt;

&lt;p&gt;The key insight is that the public URL is built from three parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base URL&lt;/strong&gt; of your server (e.g., &lt;code&gt;http://localhost:3000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static serving path&lt;/strong&gt; (e.g., &lt;code&gt;/files&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filename on disk&lt;/strong&gt; (the unique name generated by Multer)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the route handler, &lt;code&gt;req.file.filename&lt;/code&gt; gives us the exact name stored on disk. Concatenating these pieces gives a valid URL that Express will serve from the &lt;code&gt;uploads&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;For a more production‑ready approach, you'd likely construct the URL from a configuration setting, not hardcode &lt;code&gt;localhost&lt;/code&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;`http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/files/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now clients always get the correct URL regardless of the environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security considerations for uploads
&lt;/h2&gt;

&lt;p&gt;File uploads are powerful but also a potential attack vector if not handled carefully. Here are some essential safe‑handling practices that every developer should follow.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Limit allowed file types
&lt;/h3&gt;

&lt;p&gt;Never accept arbitrary file types. Use Multer's &lt;code&gt;fileFilter&lt;/code&gt; to check the MIME type or file extension before writing to disk. For example, if you expect images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fileFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowedTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/jpeg|jpg|png|gif/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mimeType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allowedTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mimetype&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allowedTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalname&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mimeType&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;extName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Only image files are allowed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Restrict file size
&lt;/h3&gt;

&lt;p&gt;You can set a &lt;code&gt;limits&lt;/code&gt; object on the Multer instance to reject files that are too large:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fileSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// 5 MB&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents someone from filling up your server's disk with a single request.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rename files to avoid path traversal
&lt;/h3&gt;

&lt;p&gt;Never trust the original filename directly in the storage path. Always generate a new name, as we did with the &lt;code&gt;filename&lt;/code&gt; callback. Using &lt;code&gt;Date.now()&lt;/code&gt; and a random number makes it nearly impossible for an attacker to guess the filename. Also avoid including user‑supplied strings in the path to prevent directory traversal attacks (e.g., &lt;code&gt;../../../etc/passwd&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Store uploaded files outside the public web root
&lt;/h3&gt;

&lt;p&gt;If possible, keep the uploads folder outside the main Express static folder that is openly served. In our setup, we used &lt;code&gt;/files&lt;/code&gt; explicitly, but the &lt;code&gt;uploads&lt;/code&gt; folder itself is not automatically exposed except through that route. Still, it's a good idea to store uploads in a directory that is not directly under the static root, and then use &lt;code&gt;express.static&lt;/code&gt; with an absolute path to serve it under a specific URL prefix. This prevents direct access via path guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Do not execute uploaded files
&lt;/h3&gt;

&lt;p&gt;Make sure your static file serving middleware only serves files with safe content types. For example, if someone uploads a &lt;code&gt;.php&lt;/code&gt; or &lt;code&gt;.js&lt;/code&gt; file and you serve it via &lt;code&gt;express.static&lt;/code&gt;, a browser might try to interpret it as code, especially if served with the wrong &lt;code&gt;Content-Type&lt;/code&gt;. Express/Node.js static middleware sends files with an appropriate MIME type based on the extension, which usually prevents execution. Still, it's wise to limit accepted extensions and avoid storing executable scripts in the uploads folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Scan for malware (advanced)
&lt;/h3&gt;

&lt;p&gt;For high‑security applications, you can integrate virus scanning tools that inspect uploaded files before they're stored. This is beyond the scope of this post, but keep it in mind for production systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Storing and serving uploaded files completes the file upload puzzle. By using Multer's disk storage and Express's static middleware, you can quickly give your users the ability to upload content and retrieve it via clean URLs. Add a few security precautions, and you have a robust foundation that works for development and small production apps.&lt;/p&gt;

&lt;p&gt;Let's quickly recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uploaded files can be stored locally in a project folder (e.g., &lt;code&gt;uploads/&lt;/code&gt;) or externally on cloud services for scalability.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;express.static&lt;/code&gt; lets you serve the files under a URL prefix, making them accessible to clients.&lt;/li&gt;
&lt;li&gt;Building a file URL combines the base URL, the static serving path, and the unique filename stored on disk.&lt;/li&gt;
&lt;li&gt;Security is essential: limit file types and sizes, rename files, avoid executable files, and store uploads outside the main public root if possible.&lt;/li&gt;
&lt;li&gt;A clear folder structure and the static middleware together create an intuitive upload‑serve flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you have the full cycle: receiving files and giving them back. In the next post, we'll explore something new, maybe working with databases or error handling. See you then!&lt;/p&gt;




&lt;p&gt;Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, where I post more about web development.&lt;/p&gt;

</description>
      <category>express</category>
      <category>multer</category>
      <category>node</category>
    </item>
    <item>
      <title>Handling File Uploads in Express with Multer</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Sun, 10 May 2026 14:00:30 +0000</pubDate>
      <link>https://dev.to/satyasootar/handling-file-uploads-in-express-with-multer-56bf</link>
      <guid>https://dev.to/satyasootar/handling-file-uploads-in-express-with-multer-56bf</guid>
      <description>&lt;p&gt;Hello readers 👋, welcome to the 12th blog in our Node.js series!&lt;/p&gt;

&lt;p&gt;In the last post, we learned how Express simplifies route handling and request processing. Today, we're going to tackle a common real‑world requirement that every backend developer faces: &lt;strong&gt;handling file uploads&lt;/strong&gt;. Whether it's a profile picture, a PDF invoice, or a batch of images, users need to send files to your server, and you need a clean way to receive and store them.&lt;/p&gt;

&lt;p&gt;Express itself doesn't handle file uploads out of the box. That's where a specialized middleware called &lt;strong&gt;Multer&lt;/strong&gt; comes in. We'll understand why file uploads need special handling, how Multer works, and then build practical examples for single and multiple file uploads. By the end, you'll be able to accept files, store them on disk, and serve them back to clients.&lt;/p&gt;

&lt;p&gt;Let's jump right in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why file uploads need middleware
&lt;/h2&gt;

&lt;p&gt;When a browser sends a file, it uses a special encoding called &lt;strong&gt;multipart/form-data&lt;/strong&gt;. This encoding splits the request body into multiple parts, each representing a form field or a file. A regular JSON body parser like &lt;code&gt;express.json()&lt;/code&gt; cannot parse multipart data. If you try, &lt;code&gt;req.body&lt;/code&gt; will be empty, and the file data will be lost.&lt;/p&gt;

&lt;p&gt;To handle multipart/form-data, we need a parser that knows how to extract files from the request stream and make them available to our code. Multer is precisely that parser.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Multer is
&lt;/h2&gt;

&lt;p&gt;Multer is a Node.js middleware for handling &lt;code&gt;multipart/form-data&lt;/code&gt;, which is primarily used for uploading files. It is designed to work with Express and sits as a middleware in the request pipeline. When a request with a file arrives, Multer processes the incoming stream, saves the file(s) to a location you specify (disk or memory), and attaches a &lt;code&gt;file&lt;/code&gt; (or &lt;code&gt;files&lt;/code&gt;) object to the &lt;code&gt;req&lt;/code&gt; object. The rest of your route handler can then access the file details.&lt;/p&gt;

&lt;p&gt;Multer adds a &lt;code&gt;file&lt;/code&gt; or &lt;code&gt;files&lt;/code&gt; property to the request object. A single file upload sets &lt;code&gt;req.file&lt;/code&gt;, while multiple files set &lt;code&gt;req.files&lt;/code&gt; (an array or an object, depending on configuration). Each file object contains fields like &lt;code&gt;originalname&lt;/code&gt;, &lt;code&gt;mimetype&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, and &lt;code&gt;path&lt;/code&gt; (if stored on disk).&lt;/p&gt;

&lt;h2&gt;
  
  
  The multipart/form-data concept simply
&lt;/h2&gt;

&lt;p&gt;Imagine you want to mail a letter along with a small gift. You can't just stuff the gift into a regular envelope; you need a special package with compartments. Similarly, when a browser sends form data that includes a file, it can't just put everything into a single JSON object. It uses &lt;code&gt;multipart/form-data&lt;/code&gt;, which creates boundaries (think of them as separators) that tell the server where each field or file begins and ends.&lt;/p&gt;

&lt;p&gt;When you set &lt;code&gt;enctype="multipart/form-data"&lt;/code&gt; on a form, the browser encodes the data using this format. Multer is the tool on the server side that understands these compartments and extracts the file from the correct part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Multer
&lt;/h2&gt;

&lt;p&gt;First, install Multer in your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;multer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, require it and configure a storage engine. We'll start with disk storage, which saves files directly to a folder on your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minimal storage configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;multer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Define where to store files and how to name them&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diskStorage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Specify the upload folder&lt;/span&gt;
    &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploads/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Give the file a unique name, preserving the extension&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uniqueSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;E9&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;uniqueSuffix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;multer.diskStorage&lt;/code&gt; lets you control the destination folder and filename. The callback &lt;code&gt;cb&lt;/code&gt; is used to pass the final path or error. We generate a semi‑unique filename to avoid collisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling a single file upload
&lt;/h2&gt;

&lt;p&gt;Let's create an Express route that accepts a single file, for example, a profile picture. The client sends the file under a field name, say &lt;code&gt;avatar&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Ensure the "uploads" folder exists, or Multer will error&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uploadsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadsDir&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadsDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Single file upload middleware&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// req.file contains information about the uploaded file&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No file uploaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;File uploaded successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;originalName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;upload.single('avatar')&lt;/code&gt; is the Multer middleware. It expects the file to be sent in the form field named &lt;code&gt;avatar&lt;/code&gt;. After successful upload, &lt;code&gt;req.file&lt;/code&gt; contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fieldname&lt;/code&gt; – the name of the form field&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;originalname&lt;/code&gt; – the original name on the user's computer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mimetype&lt;/code&gt; – e.g., &lt;code&gt;image/png&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;size&lt;/code&gt; – file size in bytes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;destination&lt;/code&gt; – the folder where the file was saved&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;filename&lt;/code&gt; – the generated filename on disk&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;path&lt;/code&gt; – the full path to the uploaded file&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Handling multiple file uploads
&lt;/h2&gt;

&lt;p&gt;Multer provides several ways to accept multiple files.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Multiple files with the same field name (array of files)
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;upload.array('photos', 5)&lt;/code&gt; to accept up to 5 files in the field &lt;code&gt;photos&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/gallery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;photos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No files uploaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileDetails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Files uploaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fileDetails&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;req.files&lt;/code&gt; is an array of file objects, each similar to &lt;code&gt;req.file&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Multiple fields with different names
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;upload.fields([...])&lt;/code&gt; when you have several distinct file fields, like &lt;code&gt;avatar&lt;/code&gt; and &lt;code&gt;cover&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cpUpload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/complete-profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cpUpload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// req.files is an object with keys 'avatar' and 'cover', each an array of files&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;avatar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;originalname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;originalname&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Serving uploaded files
&lt;/h2&gt;

&lt;p&gt;After uploading, you often need to serve the files back to the client (e.g., to display an image). Express has a built‑in middleware &lt;code&gt;express.static&lt;/code&gt; for serving static files from a directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Serve files from the 'uploads' folder at the '/files' URL path&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if a file was saved as &lt;code&gt;uploads/avatar-123456789.png&lt;/code&gt;, the client can access it at &lt;code&gt;http://localhost:3000/files/avatar-123456789.png&lt;/code&gt;. You can use the relative path to build the URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualizing the upload flow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Client → Server → Storage flow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client (browser)
  │
  ├── Sends POST /profile with multipart/form-data (file in field "avatar")
  │
  ▼
Express Server
  │
  ├── Multer middleware (upload.single('avatar'))
  │     ├── Parses multipart data
  │     ├── Streams file to disk (or memory)
  │     └── Attaches req.file
  │
  ▼
Route Handler
  │
  ├── Accesses req.file details
  ├── Saves metadata to database (if needed)
  └── Sends response (e.g., file URL)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Multer middleware execution:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
  │
  ▼
Multer middleware (configured with storage engine)
  │
  ├── Read stream → Identify boundaries
  │
  ├── For each file field:
  │     ├── Extract filename, MIME type
  │     ├── Call storage._handleFile (disk or memory)
  │     │     └── Write to disk / buffer
  │     └── Emit 'file' event or push to array
  │
  ├── Calls next() when all fields and files processed
  │
  ▼
Next middleware / route handler (receives req.file or req.files)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That summarizes the lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Multer makes handling file uploads in Express straightforward. By plugging it as a middleware, you can accept single or multiple files, control where they are saved, and then serve them back easily. Understanding multipart/form-data and Multer's role is key to building applications that accept user‑generated content.&lt;/p&gt;

&lt;p&gt;Let's quickly recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File uploads use &lt;code&gt;multipart/form-data&lt;/code&gt;; regular body parsers can't handle them.&lt;/li&gt;
&lt;li&gt;Multer is an Express middleware that parses multipart data and gives you &lt;code&gt;req.file&lt;/code&gt; or &lt;code&gt;req.files&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configure storage using &lt;code&gt;multer.diskStorage&lt;/code&gt; to define destination and filename.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;upload.single()&lt;/code&gt;, &lt;code&gt;upload.array()&lt;/code&gt;, and &lt;code&gt;upload.fields()&lt;/code&gt; cover most use cases.&lt;/li&gt;
&lt;li&gt;Serve uploaded files with &lt;code&gt;express.static&lt;/code&gt; for easy access.&lt;/li&gt;
&lt;li&gt;The upload lifecycle flows from client to Multer to disk, then to your route handler.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you can confidently add file upload capabilities to your Node.js APIs. In the next post, we might explore error handling in Multer or move on to database integration. See you then!&lt;/p&gt;




&lt;p&gt;Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, where I post more about web development.&lt;/p&gt;

</description>
      <category>express</category>
      <category>node</category>
      <category>multer</category>
    </item>
    <item>
      <title>Creating Routes and Handling Requests with Express</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Sun, 10 May 2026 13:52:33 +0000</pubDate>
      <link>https://dev.to/satyasootar/creating-routes-and-handling-requests-with-express-ibm</link>
      <guid>https://dev.to/satyasootar/creating-routes-and-handling-requests-with-express-ibm</guid>
      <description>&lt;p&gt;Hello readers 👋, welcome to the 11th blog in our Node.js series!&lt;/p&gt;

&lt;p&gt;We've come a long way. We started by setting up Node.js, understood its event loop, explored blocking vs non‑blocking code, handled async operations with promises, secured routes with JWT, designed a REST API, and even dove into middleware. But to build those APIs and middleware, we used Express.js. Now it's time to shine a focused light on exactly how Express helps us create routes and handle requests so effortlessly.&lt;/p&gt;

&lt;p&gt;In this post, we'll take a step back and talk about what Express.js is, why it makes Node.js development so much smoother, and how to create clean, readable routes for &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt; requests. By the end, you'll see why Express has become the go‑to framework for building web servers with Node.js.&lt;/p&gt;

&lt;p&gt;Let's jump in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Express.js?
&lt;/h2&gt;

&lt;p&gt;Express.js is a minimal and flexible web application framework for Node.js. It provides a thin layer of fundamental web application features, without obscuring Node.js features that you know and love. In simpler words, it's a set of tools that makes it ridiculously easy to handle HTTP requests, define routes, work with middleware, and send responses.&lt;/p&gt;

&lt;p&gt;At its core, Express is just a function that returns an application object. You attach route handlers to it, and it efficiently maps incoming requests to the right handlers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Express simplifies Node.js development
&lt;/h2&gt;

&lt;p&gt;To appreciate Express, let's quickly compare it with building an HTTP server using Node.js's built‑in &lt;code&gt;http&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw Node.js HTTP server example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Satya&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Satya&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works, but as your application grows, the code becomes a mess of &lt;code&gt;if‑else&lt;/code&gt; statements, manual body parsing, and repetitive header writing.&lt;/p&gt;

&lt;p&gt;Now look at the same logic with Express:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Satya&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Satya&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what Express gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clean routing&lt;/strong&gt;: You directly map HTTP methods and URL patterns to handler functions. No need to manually parse &lt;code&gt;req.url&lt;/code&gt; or check &lt;code&gt;req.method&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic body parsing&lt;/strong&gt;: With &lt;code&gt;express.json()&lt;/code&gt; middleware, the body is parsed and available at &lt;code&gt;req.body&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy response helpers&lt;/strong&gt;: &lt;code&gt;res.json()&lt;/code&gt; automatically sets the content type and stringifies the object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route parameters&lt;/strong&gt;: Express extracts &lt;code&gt;:id&lt;/code&gt; from the URL and places it in &lt;code&gt;req.params&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Middleware support&lt;/strong&gt;: You can chain functions (like we explored in the previous blog) to handle logging, auth, validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Express takes away the boilerplate so you can focus on your application logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating your first Express server
&lt;/h2&gt;

&lt;p&gt;Let's start from zero. First, initialize a project and install Express:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a file, say &lt;code&gt;server.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello from Express!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server is running on http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with &lt;code&gt;node server.js&lt;/code&gt;, and open &lt;code&gt;http://localhost:3000&lt;/code&gt;. You'll see the greeting. This is the absolute minimal Express server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling GET requests
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;GET&lt;/code&gt; requests are used to fetch data. Express makes it very intuitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Simple GET route
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Using route parameters
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A request to &lt;code&gt;/users/42&lt;/code&gt; returns &lt;code&gt;User ID: 42&lt;/code&gt;. Express automatically parses the &lt;code&gt;:id&lt;/code&gt; segment.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Query string parameters
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A request to &lt;code&gt;/search?q=express&amp;amp;page=2&lt;/code&gt; gives &lt;code&gt;{ "query": "express", "page": "2" }&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Returning JSON data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Satya&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Priya&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// sets Content-Type to application/json automatically&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;res.json()&lt;/code&gt; is the go‑to for API responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling POST requests
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;POST&lt;/code&gt; requests typically create new resources. To process them, you need to read the request body. Express's built-in middleware &lt;code&gt;express.json()&lt;/code&gt; does exactly that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// parse JSON bodies&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// In a real app, you'd save to a database&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test it with a client that sends JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Rahul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rahul@example.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response will be &lt;code&gt;201 Created&lt;/code&gt; with the new user object.&lt;/p&gt;

&lt;p&gt;You can also handle form submissions using &lt;code&gt;express.urlencoded({ extended: true })&lt;/code&gt; if you're dealing with traditional HTML forms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending responses
&lt;/h2&gt;

&lt;p&gt;Express provides multiple methods to send responses, each suitable for different scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;res.send(string)&lt;/code&gt; – sends a plain text or HTML string; sets &lt;code&gt;Content-Type&lt;/code&gt; accordingly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;res.json(object)&lt;/code&gt; – sends a JSON object with proper header.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;res.status(code).json(object)&lt;/code&gt; – sets the status code and sends JSON.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;res.sendFile(path)&lt;/code&gt; – streams a file from disk.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;res.redirect(url)&lt;/code&gt; – redirects to another URL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;res.download(path)&lt;/code&gt; – prompts the browser to download a file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also chain status codes: &lt;code&gt;res.status(404).send('Not Found')&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/sample.pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Express is flexible and doesn't force one way of responding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Express.js strips away the repetitive plumbing of raw Node.js HTTP servers and lets you define routes declaratively, making your code cleaner and faster to write. With just a few lines, you can set up a server, handle GET and POST requests, parse inputs, and send proper responses. This is the foundation of every Node.js backend I've built.&lt;/p&gt;

&lt;p&gt;Let's recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Express is a minimal framework that simplifies creating HTTP servers in Node.js.&lt;/li&gt;
&lt;li&gt;It offers clean routing with methods like &lt;code&gt;app.get&lt;/code&gt;, &lt;code&gt;app.post&lt;/code&gt;, &lt;code&gt;app.put&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Route parameters (&lt;code&gt;req.params&lt;/code&gt;), query strings (&lt;code&gt;req.query&lt;/code&gt;), and body parsing (&lt;code&gt;req.body&lt;/code&gt;) are built in.&lt;/li&gt;
&lt;li&gt;Response helpers (&lt;code&gt;res.json&lt;/code&gt;, &lt;code&gt;res.send&lt;/code&gt;, &lt;code&gt;res.status&lt;/code&gt;) make sending data back straightforward.&lt;/li&gt;
&lt;li&gt;Compared to raw Node, Express eliminates boilerplate and reduces developer friction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that you're comfortable with routes and request handling, the next step is to build more complex applications, connect to databases, and structure larger codebases. We'll continue that journey in the next post.&lt;/p&gt;




&lt;p&gt;Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, where I post more about web development.&lt;/p&gt;

</description>
      <category>express</category>
      <category>routes</category>
      <category>node</category>
    </item>
    <item>
      <title>What is Middleware in Express and How It Works</title>
      <dc:creator>SATYA SOOTAR</dc:creator>
      <pubDate>Sun, 10 May 2026 13:50:59 +0000</pubDate>
      <link>https://dev.to/satyasootar/what-is-middleware-in-express-and-how-it-works-47an</link>
      <guid>https://dev.to/satyasootar/what-is-middleware-in-express-and-how-it-works-47an</guid>
      <description>&lt;p&gt;Hello readers 👋, welcome to the 10th blog in our Node.js series!&lt;/p&gt;

&lt;p&gt;In the last post, we built a clean REST API using Express.js for a users resource. We defined routes and handled different HTTP methods. Today, we are going to explore a concept that sits quietly at the heart of every Express application: &lt;strong&gt;middleware&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you have ever wanted to log every request, check authentication tokens, or validate incoming data before it hits your route handler, you have needed middleware. Let's understand what middleware is, how it fits into the request lifecycle, the different types, and how to chain multiple middleware to build powerful, reusable pipelines.&lt;/p&gt;

&lt;p&gt;Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  What middleware is in Express
&lt;/h2&gt;

&lt;p&gt;Middleware, in Express, are functions that have access to the request object (&lt;code&gt;req&lt;/code&gt;), the response object (&lt;code&gt;res&lt;/code&gt;), and the next middleware function in the application's request-response cycle. The next middleware function is conventionally denoted by a variable named &lt;code&gt;next&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Think of middleware as a series of checkpoints or processing stations that every incoming request passes through before reaching the final route handler. At each checkpoint, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspect or modify the request (e.g., add properties, parse the body).&lt;/li&gt;
&lt;li&gt;Perform some logic (e.g., log the request time, check authentication).&lt;/li&gt;
&lt;li&gt;Send an early response (e.g., return an error if the user is not authenticated) and stop the chain.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;next()&lt;/code&gt; to pass control to the next middleware in line.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Middleware functions are executed in the order they are defined. That order is crucial and we'll explore it soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where middleware sits in the request lifecycle
&lt;/h2&gt;

&lt;p&gt;In a typical Express app, an incoming HTTP request flows through a pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The request arrives at the server.&lt;/li&gt;
&lt;li&gt;It passes through each middleware function registered (both application-level and router-level) in the order they were added.&lt;/li&gt;
&lt;li&gt;Finally, it reaches the route handler that matches the path and method.&lt;/li&gt;
&lt;li&gt;The route handler sends a response.&lt;/li&gt;
&lt;li&gt;If no middleware or route sends a response, the request hangs; Express doesn't automatically respond.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The middleware chain can be visualized as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request → [Middleware 1] → [Middleware 2] → [Route Handler] → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each arrow is a &lt;code&gt;next()&lt;/code&gt; call. If a middleware function does not call &lt;code&gt;next()&lt;/code&gt;, the request stalls and never reaches the next middleware or route handler (unless you send a response and end the request). This pipeline concept is the backbone of Express.&lt;/p&gt;

&lt;h2&gt;
  
  
  The role of &lt;code&gt;next()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;next&lt;/code&gt; function is a callback that tells Express "I'm done, move on to the next middleware or route handler." If the currently executing middleware function does not end the request-response cycle, it must call &lt;code&gt;next()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are two important variations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;next()&lt;/code&gt; – proceed to the next middleware in the stack.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;next('route')&lt;/code&gt; – skip to the next route handler (only works in router-level middleware).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you pass an error to &lt;code&gt;next(err)&lt;/code&gt;, Express skips all remaining non-error middleware and goes directly to an error-handling middleware, which has four parameters: &lt;code&gt;(err, req, res, next)&lt;/code&gt;. We'll keep error handling middleware for another post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application-level middleware
&lt;/h2&gt;

&lt;p&gt;Application-level middleware are bound to an instance of the &lt;code&gt;app&lt;/code&gt; object using &lt;code&gt;app.use()&lt;/code&gt; or &lt;code&gt;app.METHOD()&lt;/code&gt;. They run for every request (if no path is specified) or for requests that match a specific path prefix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: logging middleware for every request&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Application-level middleware (no path, runs on every request)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we log the HTTP method, URL, and timestamp for every request. We then call &lt;code&gt;next()&lt;/code&gt; so the request continues down the pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: middleware for a specific path&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can limit middleware to a path prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Admin route accessed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Middleware added this way will run for any route starting with &lt;code&gt;/admin&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Router-level middleware
&lt;/h2&gt;

&lt;p&gt;Router-level middleware works exactly like application-level middleware, except it is bound to an instance of &lt;code&gt;express.Router()&lt;/code&gt;. It's used to organize route-specific middleware, especially when you have modular route files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Router-level middleware: runs for every request handled by this router&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inside user router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, any request starting with &lt;code&gt;/users&lt;/code&gt; will first go through the router-level middleware before hitting the specific route handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in middleware
&lt;/h2&gt;

&lt;p&gt;Express has some built-in middleware functions that solve common tasks. You've already seen one: &lt;code&gt;express.json()&lt;/code&gt;. These are middleware functions that come with Express and are used via &lt;code&gt;app.use()&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;express.json()&lt;/code&gt; – parses incoming JSON payloads and populates &lt;code&gt;req.body&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;express.urlencoded({ extended: true })&lt;/code&gt; – parses URL-encoded payloads (from HTML forms).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;express.static('public')&lt;/code&gt; – serves static files (images, CSS, JS) from a directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are middleware too! They process the request and call &lt;code&gt;next()&lt;/code&gt; for the next piece in the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execution order of middleware
&lt;/h2&gt;

&lt;p&gt;The order in which you declare middleware matters immensely. Express executes them sequentially, top to bottom. If you place a middleware that sends a response before your route handler, the route handler will never run.&lt;/p&gt;

&lt;p&gt;Consider this incorrect example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This ends the request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// This will still be called but no further middleware can modify the response after send&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the first middleware sends a response, and unless you call &lt;code&gt;next()&lt;/code&gt; before &lt;code&gt;send()&lt;/code&gt;, the &lt;code&gt;get&lt;/code&gt; handler never sees the request. More importantly, even if you call &lt;code&gt;next()&lt;/code&gt; after &lt;code&gt;send()&lt;/code&gt;, Express will not allow you to send another response; you'd get an error because headers are already sent. So, typically you either call &lt;code&gt;next()&lt;/code&gt; without sending a response, or you end the request inside the middleware.&lt;/p&gt;

&lt;p&gt;The correct pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Logging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// pass control&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// maybe add timestamp to req&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Hello world, request at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestTime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each middleware adds something and passes control. The route handler runs at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world examples
&lt;/h2&gt;

&lt;p&gt;Let's see three practical middleware: logging, authentication, and request validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logging middleware
&lt;/h3&gt;

&lt;p&gt;You've already seen a basic logger. Let's write a slightly cleaner one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;finish&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This logs the method, URL, status code, and response time. It uses the &lt;code&gt;finish&lt;/code&gt; event on the response to know when the response is sent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication middleware
&lt;/h3&gt;

&lt;p&gt;This one checks for a valid JWT token (similar to our earlier JWT blog). If invalid, it stops the request with a 401 error. Otherwise, it attaches the user and continues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Access token missing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Protect a route&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User profile for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the middleware stops the request (by returning a response) when authentication fails, so the route handler never executes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request validation middleware
&lt;/h3&gt;

&lt;p&gt;You can validate input before it reaches business logic. This keeps your route handlers clean. Here's a simple one for creating a user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateUserInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name and email are required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateUserInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// create user logic...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When validation fails, we respond immediately with &lt;code&gt;400 Bad Request&lt;/code&gt;. Otherwise, &lt;code&gt;next()&lt;/code&gt; passes control to the actual handler. This separation improves readability and reusability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Middleware pipeline
&lt;/h2&gt;

&lt;p&gt;Imagine a physical pipeline with several inspection stations. A request arrives, gets stamped at the logging station, then passes through a security gate (authentication), then a quality check (validation), and finally reaches the desired destination (route handler). At any point, if it fails inspection, it gets turned away with a response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyoqyfrgrdt4xx4edno2i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyoqyfrgrdt4xx4edno2i.png" alt="Middleware pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Middleware is one of those concepts that, once you understand it, Express becomes transparent. It's just a sequence of functions that have access to &lt;code&gt;req&lt;/code&gt; and &lt;code&gt;res&lt;/code&gt; and can either pass the request along with &lt;code&gt;next()&lt;/code&gt; or end the cycle by sending a response. By chaining middleware, you can separate concerns like logging, authentication, and validation, making your code cleaner and more maintainable.&lt;/p&gt;

&lt;p&gt;Let's quickly recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Middleware functions sit between the incoming request and the final route handler.&lt;/li&gt;
&lt;li&gt;They can modify the request/response, run code, or end the request early.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;next()&lt;/code&gt; function passes control to the next middleware in the stack; without it, the request stalls.&lt;/li&gt;
&lt;li&gt;Application-level middleware (&lt;code&gt;app.use()&lt;/code&gt;) runs for all requests or a specific path.&lt;/li&gt;
&lt;li&gt;Router-level middleware (&lt;code&gt;router.use()&lt;/code&gt;) works for a specific router.&lt;/li&gt;
&lt;li&gt;Built-in middleware like &lt;code&gt;express.json()&lt;/code&gt; are just prepackaged middleware.&lt;/li&gt;
&lt;li&gt;Execution order is top-to-bottom, so declare middleware before routes they should affect.&lt;/li&gt;
&lt;li&gt;Real-world uses include logging, authentication, and input validation, which keep route handlers focused.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that you understand middleware, you can structure Express apps that are modular, secure, and easy to scale. In the next post, we'll continue building more practical features. See you there!&lt;/p&gt;




&lt;p&gt;Hope you found this helpful! If you spot any mistakes or have suggestions, let me know. You can find me on &lt;a href="https://www.linkedin.com/in/satyaprangyasootar" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/satyasootar" rel="noopener noreferrer"&gt;X&lt;/a&gt;, where I post more about web development.&lt;/p&gt;

</description>
      <category>express</category>
      <category>node</category>
    </item>
  </channel>
</rss>
