<?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: Hardik Agarwal</title>
    <description>The latest articles on DEV Community by Hardik Agarwal (@hardik-agarwal18).</description>
    <link>https://dev.to/hardik-agarwal18</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3846562%2F917d98cf-fac7-4464-9d54-2fe5ef49efdb.jpeg</url>
      <title>DEV Community: Hardik Agarwal</title>
      <link>https://dev.to/hardik-agarwal18</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hardik-agarwal18"/>
    <language>en</language>
    <item>
      <title>Designing a URL Shortener: My First High-Level System Design</title>
      <dc:creator>Hardik Agarwal</dc:creator>
      <pubDate>Sat, 24 Oct 2026 23:03:08 +0000</pubDate>
      <link>https://dev.to/hardik-agarwal18/designing-a-url-shortener-my-first-high-level-system-design-ngn</link>
      <guid>https://dev.to/hardik-agarwal18/designing-a-url-shortener-my-first-high-level-system-design-ngn</guid>
      <description>&lt;p&gt;&lt;em&gt;Exploring API design, Redis caching, database capacity, horizontal scaling, and asynchronous analytics.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;System design has always seemed like one of those skills that becomes easier only when you start building systems on paper.&lt;/p&gt;

&lt;p&gt;So, I decided to start practicing High-Level Design (HLD) by designing a familiar application: a URL shortener.&lt;/p&gt;

&lt;p&gt;At first glance, a URL shortener looks simple. You provide a long URL, receive a short link, and redirect users when they visit it.&lt;/p&gt;

&lt;p&gt;But what happens when millions of users create links and billions of redirects need to be processed? How do we keep redirects fast? How do we handle traffic spikes? And how do we collect analytics without slowing down the redirect path?&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fus4eu59nsv07y5ele9rj.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fus4eu59nsv07y5ele9rj.png" alt=" " width="800" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, I'll walk through my first-pass design, the reasoning behind the architecture, and the trade-offs I need to understand better.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Understanding the requirements
&lt;/h2&gt;

&lt;p&gt;Before drawing boxes and connecting services, I started by defining what the system needs to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Functional requirements
&lt;/h3&gt;

&lt;p&gt;The initial scope includes two core features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a short code for an original URL.&lt;/li&gt;
&lt;li&gt;Redirect a short URL to its original destination.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click analytics are also included as an asynchronous supporting feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Non-functional requirements
&lt;/h3&gt;

&lt;p&gt;The system should aim for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High availability.&lt;/li&gt;
&lt;li&gt;Low redirect latency.&lt;/li&gt;
&lt;li&gt;Horizontal scalability.&lt;/li&gt;
&lt;li&gt;Durable URL mappings.&lt;/li&gt;
&lt;li&gt;Asynchronous analytics processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this exercise, I set illustrative latency targets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;p95 target&lt;/th&gt;
&lt;th&gt;p99 target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create URL&lt;/td&gt;
&lt;td&gt;600 ms&lt;/td&gt;
&lt;td&gt;800 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redirect&lt;/td&gt;
&lt;td&gt;100 ms&lt;/td&gt;
&lt;td&gt;200 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are design targets, not measured results. Actual latency depends on deployment region, network conditions, cache performance, and infrastructure.&lt;/p&gt;

&lt;p&gt;I also assumed that URLs expire after seven days by default for this initial design.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The proposed architecture
&lt;/h2&gt;

&lt;p&gt;The architecture contains the following components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client&lt;/li&gt;
&lt;li&gt;NGINX / load balancer&lt;/li&gt;
&lt;li&gt;Horizontally scaled URL service&lt;/li&gt;
&lt;li&gt;Redis cache&lt;/li&gt;
&lt;li&gt;Primary database&lt;/li&gt;
&lt;li&gt;Message queue&lt;/li&gt;
&lt;li&gt;Analytics worker and analytics database&lt;/li&gt;
&lt;li&gt;Prometheus and Grafana&lt;/li&gt;
&lt;li&gt;A scheduled cleanup job&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The main idea is to keep the redirect path fast while moving work that doesn't need to happen immediately into background processing.&lt;/p&gt;

&lt;p&gt;The request flow looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL creation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Client → Load Balancer → URL Service → Primary Database → Return Short URL&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL redirection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Client → Load Balancer → URL Service → Redis Cache → Redirect&lt;/p&gt;

&lt;p&gt;If the cache misses, the URL service reads from the primary database and can populate the cache for subsequent requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Click analytics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;URL Service → Message Queue → Analytics Worker → Analytics Database&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;URL Service and supporting components → Prometheus → Grafana&lt;/p&gt;

&lt;p&gt;Each component solves a different problem, and understanding why it exists is more important than simply memorizing the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why do we need a load balancer?
&lt;/h2&gt;

&lt;p&gt;A single application server can become a bottleneck as traffic grows.&lt;/p&gt;

&lt;p&gt;Instead of directing every request to one server, the load balancer distributes requests across multiple instances of the URL service.&lt;/p&gt;

&lt;p&gt;For example, we might initially run three application instances.&lt;/p&gt;

&lt;p&gt;This gives us several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can add instances as traffic increases.&lt;/li&gt;
&lt;li&gt;A failed instance can be removed from the available pool.&lt;/li&gt;
&lt;li&gt;Requests can be distributed across healthy instances.&lt;/li&gt;
&lt;li&gt;We can deploy updated instances without necessarily taking the entire service offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The load balancer doesn't make the whole system highly available by itself. The application instances, database, cache, and other dependencies must also be designed to tolerate failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Why Redis is important
&lt;/h2&gt;

&lt;p&gt;This was one of the most interesting parts of the design for me.&lt;/p&gt;

&lt;p&gt;A URL shortener receives many redirect requests, and some short links may be accessed far more often than others.&lt;/p&gt;

&lt;p&gt;If every redirect required a database query, the primary database would handle a large amount of repetitive work.&lt;/p&gt;

&lt;p&gt;Redis provides a fast in-memory lookup layer for frequently accessed URL mappings.&lt;/p&gt;

&lt;p&gt;A simplified cache entry might look like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;short_code → original_url&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aB91xZ → https://example.com/articles/system-design&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache hit
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;The URL service receives a redirect request.&lt;/li&gt;
&lt;li&gt;It checks Redis using the short code.&lt;/li&gt;
&lt;li&gt;Redis returns the original URL.&lt;/li&gt;
&lt;li&gt;The service responds with an HTTP redirect.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cache miss
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Redis doesn't contain the requested mapping.&lt;/li&gt;
&lt;li&gt;The URL service queries the primary database.&lt;/li&gt;
&lt;li&gt;If the mapping exists and is valid, the service returns the redirect and populates the cache.&lt;/li&gt;
&lt;li&gt;Future requests may be served directly from Redis.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Expired or missing URLs should not be treated as valid destinations.&lt;/p&gt;

&lt;p&gt;In a production system, I would also consider cache stampedes, negative caching, eviction policies, and what happens when Redis becomes unavailable.&lt;/p&gt;

&lt;p&gt;The key lesson: &lt;strong&gt;Redis is a performance optimization, while the primary database remains the durable source of URL mappings.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Choosing the primary database
&lt;/h2&gt;

&lt;p&gt;The primary database stores the authoritative URL mapping.&lt;/p&gt;

&lt;p&gt;A simplified schema could look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Primary key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;original_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Original destination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;short_code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unique, indexed short code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creation timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expires_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expiration timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;short_code&lt;/code&gt; must be unique. Otherwise, two different URLs could end up sharing the same short link.&lt;/p&gt;

&lt;p&gt;If a generated code collides with an existing code, the service needs to retry or use another collision-resolution strategy.&lt;/p&gt;

&lt;p&gt;I would also index &lt;code&gt;short_code&lt;/code&gt; because redirect requests use it for lookups.&lt;/p&gt;

&lt;p&gt;The database choice depends on the workload and consistency requirements. A relational database is a reasonable starting point because it supports uniqueness constraints and durable storage. At larger scales, partitioning, replication, and the distribution of data would need further investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Estimating traffic before choosing infrastructure
&lt;/h2&gt;

&lt;p&gt;Capacity estimation is where system design becomes more than drawing architecture diagrams.&lt;/p&gt;

&lt;p&gt;For this exercise, I used the following assumptions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Assumption&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total registered users&lt;/td&gt;
&lt;td&gt;100 million&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily active users&lt;/td&gt;
&lt;td&gt;10 million&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL creations per active user per day&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redirects per creation&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seconds per day&lt;/td&gt;
&lt;td&gt;86,400&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are illustrative assumptions rather than actual usage statistics.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL creation requests
&lt;/h3&gt;

&lt;p&gt;Daily creations:&lt;/p&gt;

&lt;p&gt;10 million × 5 = 50 million creations/day.&lt;/p&gt;

&lt;p&gt;Average creation requests per second:&lt;/p&gt;

&lt;p&gt;50,000,000 ÷ 86,400 ≈ 579 requests/second.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redirect requests
&lt;/h3&gt;

&lt;p&gt;Assuming a 100:1 redirect-to-creation ratio:&lt;/p&gt;

&lt;p&gt;50 million × 100 = 5 billion redirects/day.&lt;/p&gt;

&lt;p&gt;Average redirect requests per second:&lt;/p&gt;

&lt;p&gt;5,000,000,000 ÷ 86,400 ≈ 57,870 requests/second.&lt;/p&gt;

&lt;p&gt;That means the system needs to handle substantially more redirects than creation requests.&lt;/p&gt;

&lt;p&gt;This is an important design insight: &lt;strong&gt;the redirect path deserves particular attention because it carries most of the traffic in this workload.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Average traffic is not enough for infrastructure sizing. If peak traffic reaches ten times the average, the redirect path could approach 579,000 requests per second under these assumptions.&lt;/p&gt;

&lt;p&gt;That would require additional capacity planning, load testing, and careful consideration of cache efficiency and database bottlenecks.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Estimating database storage
&lt;/h2&gt;

&lt;p&gt;Next, I estimated how much space URL mappings might occupy.&lt;/p&gt;

&lt;p&gt;Suppose the average record and its relevant index overhead consume approximately 138 bytes. This is a simplified planning estimate, not a measured database row size.&lt;/p&gt;

&lt;p&gt;For 500 million mappings:&lt;/p&gt;

&lt;p&gt;500,000,000 × 138 bytes ≈ 69 GB.&lt;/p&gt;

&lt;p&gt;This is a rough estimate for the assumed per-entry footprint. Actual storage will vary based on URL lengths, index structures, database format, replication, metadata, and storage overhead.&lt;/p&gt;

&lt;p&gt;A production capacity estimate should also account for backups, replicas, growth, and operational headroom.&lt;/p&gt;

&lt;p&gt;The takeaway is that storage calculations should begin with explicit assumptions and be refined using measurements from the actual database.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Why asynchronous analytics?
&lt;/h2&gt;

&lt;p&gt;A click event is useful for understanding how often links are accessed and how traffic changes over time.&lt;/p&gt;

&lt;p&gt;However, I don't want the redirect request to wait for analytics aggregation or database writes.&lt;/p&gt;

&lt;p&gt;Instead, the URL service publishes a click event to a message queue, and an analytics worker processes it independently.&lt;/p&gt;

&lt;p&gt;The worker can aggregate information and write it to a separate analytics database.&lt;/p&gt;

&lt;p&gt;This separation provides several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analytics processing doesn't need to run synchronously with each redirect.&lt;/li&gt;
&lt;li&gt;Workers can scale independently.&lt;/li&gt;
&lt;li&gt;Events can be retried after transient processing failures.&lt;/li&gt;
&lt;li&gt;Analytics storage can be optimized for reporting rather than URL lookups.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are trade-offs, too. Analytics may be delayed, queues require monitoring, and retries can produce duplicate events unless processing is designed to be idempotent.&lt;/p&gt;

&lt;p&gt;The exact delivery guarantees depend on the queue and consumer implementation.&lt;/p&gt;

&lt;p&gt;For this first design, the primary database is responsible for URL mappings, while the analytics database is responsible for click statistics.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Expiration and background cleanup
&lt;/h2&gt;

&lt;p&gt;The initial requirement gives each URL a default lifetime of seven days.&lt;/p&gt;

&lt;p&gt;A scheduled job can periodically identify expired mappings and remove them from the primary database.&lt;/p&gt;

&lt;p&gt;But deleting a record is only one part of expiration handling.&lt;/p&gt;

&lt;p&gt;The service must also ensure that expired URLs aren't served from Redis. Otherwise, a stale cache entry could continue redirecting users after the database record has expired.&lt;/p&gt;

&lt;p&gt;Possible approaches include cache TTLs aligned with URL expiration, expiration checks during lookup, and invalidation when records are deleted.&lt;/p&gt;

&lt;p&gt;For this exercise, I placed a cleanup job in the architecture that runs every three hours. That interval is a starting assumption, not a guarantee that every expired mapping disappears immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Observability with Prometheus and Grafana
&lt;/h2&gt;

&lt;p&gt;A system can appear healthy while users experience slow redirects or requests begin failing.&lt;/p&gt;

&lt;p&gt;I included Prometheus and Grafana to make the system's behavior visible.&lt;/p&gt;

&lt;p&gt;Some useful metrics include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request rate and error rate for the URL service.&lt;/li&gt;
&lt;li&gt;Redirect latency at p50, p95, and p99.&lt;/li&gt;
&lt;li&gt;Redis hit ratio and latency.&lt;/li&gt;
&lt;li&gt;Primary database query latency and connection utilization.&lt;/li&gt;
&lt;li&gt;Queue depth and event-processing delay.&lt;/li&gt;
&lt;li&gt;Analytics worker failures and retry counts.&lt;/li&gt;
&lt;li&gt;CPU, memory, and instance health.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prometheus collects metrics, while Grafana visualizes them through dashboards and alerts.&lt;/p&gt;

&lt;p&gt;I would also add structured logs and distributed tracing where appropriate. Metrics tell us that something is wrong; logs and traces help us investigate why.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Trade-offs and questions for the next iteration
&lt;/h2&gt;

&lt;p&gt;This is a first-pass architecture, not a finished production design. Several questions still need deeper analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How should short codes be generated?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Random codes, counter-based IDs, and IDs encoded using Base62 have different trade-offs involving collisions, predictability, coordination, and distribution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when Redis fails?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The service may need to fall back to the database, but an uncontrolled fallback could overwhelm it. Timeouts, circuit breakers, concurrency limits, and graceful degradation would need consideration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we handle very popular URLs?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A small number of links could account for a large fraction of traffic. Cache strategy, replication, and hot-key handling become important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we guarantee reliable analytics events?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Publishing events introduces failure scenarios between serving a redirect and recording its click. A more detailed design should establish the required delivery guarantees and consider durable event publication patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should we partition the database?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That decision should follow evidence about storage growth, query performance, write throughput, and operational constraints rather than being introduced automatically.&lt;/p&gt;

&lt;p&gt;These are the areas I want to explore in future iterations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Designing a URL shortener helped me connect several backend concepts that are often learned separately: caching, database indexing, load balancing, asynchronous processing, capacity estimation, and observability.&lt;/p&gt;

&lt;p&gt;The biggest lesson was that a system design isn't just a collection of technologies. Every component should have a clear purpose, and every major decision should have an explanation and a trade-off.&lt;/p&gt;

&lt;p&gt;This is my first iteration, and there is plenty left to improve.&lt;/p&gt;

&lt;p&gt;I'm continuing to practice system design by choosing familiar products, defining requirements, estimating scale, drawing architectures, and investigating the trade-offs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'd love feedback from engineers and fellow learners: What would you change first in this design, and why?&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  SystemDesign #BackendEngineering #SoftwareArchitecture #DistributedSystems #LearningInPublic
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>performance</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>npm run dev vs npm start: The Difference That Breaks Your App in Production</title>
      <dc:creator>Hardik Agarwal</dc:creator>
      <pubDate>Mon, 30 Mar 2026 18:12:07 +0000</pubDate>
      <link>https://dev.to/hardik-agarwal18/npm-run-dev-vs-npm-start-the-difference-that-breaks-your-app-in-production-1372</link>
      <guid>https://dev.to/hardik-agarwal18/npm-run-dev-vs-npm-start-the-difference-that-breaks-your-app-in-production-1372</guid>
      <description>&lt;p&gt;Everything was working perfectly.&lt;/p&gt;

&lt;p&gt;I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And my app felt amazing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reloaded instantly&lt;/li&gt;
&lt;li&gt;It never stayed broken&lt;/li&gt;
&lt;li&gt;Errors? Fixed themselves after save&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honestly, it felt like I had built something solid.&lt;/p&gt;

&lt;p&gt;Then I deployed it.&lt;/p&gt;

&lt;p&gt;And everything fell apart.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Went Wrong?
&lt;/h2&gt;

&lt;p&gt;The app started crashing.&lt;/p&gt;

&lt;p&gt;Sometimes it just stopped responding.&lt;br&gt;
Sometimes logs didn’t show anything useful.&lt;br&gt;
And the worst part?&lt;/p&gt;

&lt;p&gt;It didn’t recover on its own.&lt;/p&gt;

&lt;p&gt;I kept thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“But it was working perfectly on my machine…”&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  The Tiny Difference That Caused All This
&lt;/h2&gt;

&lt;p&gt;It came down to one small change:&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="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodemon index.js"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"start"&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 index.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;Just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nodemon&lt;/code&gt; in development&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;node&lt;/code&gt; in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, this looks harmless.&lt;/p&gt;

&lt;p&gt;But it changes &lt;strong&gt;everything&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What nodemon Actually Does (And Why It Tricks You)
&lt;/h2&gt;

&lt;p&gt;When you use &lt;code&gt;nodemon&lt;/code&gt;, your app behaves very differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It watches your files&lt;/li&gt;
&lt;li&gt;It restarts automatically on every change&lt;/li&gt;
&lt;li&gt;It even restarts after crashes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So in development, your app feels… &lt;strong&gt;immortal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You break something? Save → fixed.&lt;br&gt;
Server crashes? It comes back instantly.&lt;/p&gt;

&lt;p&gt;You never really see failure.&lt;/p&gt;


&lt;h2&gt;
  
  
  But Production Is Not That Forgiving
&lt;/h2&gt;

&lt;p&gt;When you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things are very different.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No auto restart&lt;/li&gt;
&lt;li&gt;No file watching&lt;/li&gt;
&lt;li&gt;No safety net&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your app crashes…&lt;/p&gt;

&lt;p&gt;👉 it stays down.&lt;/p&gt;

&lt;p&gt;No warning. No recovery. Just silence.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mistake Most Developers Make
&lt;/h2&gt;

&lt;p&gt;We get used to this loop:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Code → Save → Auto restart → Everything works&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And we assume:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Yeah, this app is stable.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the truth is:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;nodemon was hiding your problems&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Issues That Only Show Up in Production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Crashes are permanent
&lt;/h3&gt;

&lt;p&gt;In dev:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Crash → nodemon restarts → you move on&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In prod:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Crash → your server is dead&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  2. You don’t have a process manager
&lt;/h3&gt;

&lt;p&gt;Running this in production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is risky.&lt;/p&gt;

&lt;p&gt;If it crashes, nobody brings it back.&lt;/p&gt;

&lt;p&gt;That’s why real systems use tools like PM2.&lt;/p&gt;

&lt;p&gt;They:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restart your app automatically&lt;/li&gt;
&lt;li&gt;Keep logs&lt;/li&gt;
&lt;li&gt;Handle multiple instances&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Your environment is different (and you forgot)
&lt;/h3&gt;

&lt;p&gt;In development, everything is relaxed.&lt;/p&gt;

&lt;p&gt;In production, you should be running:&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="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But many people forget this.&lt;/p&gt;

&lt;p&gt;And suddenly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debug logs flood your system&lt;/li&gt;
&lt;li&gt;Performance drops&lt;/li&gt;
&lt;li&gt;Sensitive info might leak&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. Logging suddenly becomes useless
&lt;/h3&gt;

&lt;p&gt;In dev:&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;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="s2"&gt;works great&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;In prod:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logs are messy or missing&lt;/li&gt;
&lt;li&gt;Debugging becomes painful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You realize:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“console.log is not a logging strategy.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  5. You never tested real conditions
&lt;/h3&gt;

&lt;p&gt;Your app worked with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 user&lt;/li&gt;
&lt;li&gt;0 pressure&lt;/li&gt;
&lt;li&gt;perfect conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But production?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100+ users&lt;/li&gt;
&lt;li&gt;concurrent requests&lt;/li&gt;
&lt;li&gt;unpredictable failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tools like Autocannon exist for a reason.&lt;/p&gt;

&lt;p&gt;Because real systems behave very differently under load.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Lesson
&lt;/h2&gt;

&lt;p&gt;The problem is not &lt;code&gt;nodemon&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem is this assumption:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If it works in development, it will work in production.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s not true.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Should Actually Do
&lt;/h2&gt;

&lt;p&gt;Here’s a better setup:&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="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodemon index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&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 index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pm2 start index.js"&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;And more importantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;nodemon&lt;/code&gt; only for development&lt;/li&gt;
&lt;li&gt;Always test using &lt;code&gt;node&lt;/code&gt; before deploying&lt;/li&gt;
&lt;li&gt;Use a process manager in production&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;NODE_ENV=production&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Don’t rely on auto-restarts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  One Line That Changed My Thinking
&lt;/h2&gt;

&lt;p&gt;After going through all this, I realized:&lt;/p&gt;

&lt;p&gt;👉 &lt;code&gt;npm run dev&lt;/code&gt; helps you build fast&lt;br&gt;
👉 &lt;code&gt;npm start&lt;/code&gt; decides if your app survives&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Development is comfortable.&lt;/p&gt;

&lt;p&gt;It fixes your mistakes.&lt;br&gt;
It hides your crashes.&lt;br&gt;
It makes everything feel smooth.&lt;/p&gt;

&lt;p&gt;Production does the opposite.&lt;/p&gt;

&lt;p&gt;It exposes everything.&lt;/p&gt;

&lt;p&gt;So next time your app works perfectly on your machine…&lt;/p&gt;

&lt;p&gt;Pause for a second and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Would this still work… without nodemon watching my back?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question alone can save you hours of debugging.&lt;/p&gt;

&lt;p&gt;—and maybe even your production system.&lt;/p&gt;

</description>
      <category>node</category>
      <category>npm</category>
      <category>nodemon</category>
    </item>
  </channel>
</rss>
