Intro
Hey everyone!🤗 If you're a beginner who's just started learning the backend, or maybe you already know the basics but want a deeper understanding of the logic behind servers, routing requests and all those fundamentals concepts, I truly hope this article helps you out.🥰
It's totally normal to feel confused at first or not to grasp everything immediatly, you'll figure it out eventually at the right time! Perhaps this article will clarify things, or maybe it will just confirm what you already know!
We will discuss the following concepts:
- Routing: Frontend vs Backend
- From Source to Server
- Development vs Production: More Than Just a Command
- Order Matters: Handling Critical Paths in Express
Routing: Frontent vs Backend
Routing means deciding which piece of code or which page to return when a user accesses a specific URL (a path, or "route"). Here lies the first major difference between the two worlds.
Frontend Routing (Vite, React Router)
When we work with a modern frontend framework (like React with Vite or Create React App), we don't handle the routing of static files:
→ The Dev Server is Smart: Vite's development server is designed to do one thing: help us code fast. It knows how to read our source files (those in src/) and serve them automatically under the corresponding URLs.
→ Automatic Routing: If our HTML or React code refers to /styles/main.css, the Vite server knows immediately where to find the physical file (in its memory or the file system) and sends it instantly to the browser. We don't write special code for this❗
→ Focus on Application Logic: Using libraries like React Router, we only focus on the application logic (e.g., "If the URL is /profile, display the Profile component"). This routing happens in the browser, after the JavaScript files have been loaded.
Manual Routing in the Backend (Express.js)
Express.js is just a minimal framework. It makes no assumptions and has no built-in "magic."
→ Routing is Manual: Once we put Express.js in charge as the main server, we must explicitly tell it what to do for every request that reaches it.
Do we want the POST /api/login request to verify the password in the database❓ We must write app.post('/api/login',...).
Do we want the GET /api/users request to return a list of users❓ We must write app.get('/api/users', ...).
→ Ignorance of Static Files: Without instructions, if it receives a request for /main.css, Express doesn't know this is a static file. It will look for an API route or rendering logic. Since it doesn't find one, it implicitly returns 404 Not Found.
When starting out, the Vite Development Server performs silent magic, it's automatically handling all the routing and serving of our static files (like CSS and images) directly from memory.
However, when we introduce Express.js, the magic stops. Express is ignorant and requires explicit instructions. We must manually define our API routes (app.get('/api/data')) and, most importantly, we must use express.static() to manually tell Express: "This is where you can find all the static files you need for the web page." Without this manual step, our CSS and JavaScript files will be invisible to the server, resulting in broken pages❗
From Source to Server
Static Files in Development:
During development, when we run npm run dev in our React project:
→ No Physical Folder: The Vite server reads our files (.jsx, .css) directly from the src/ directory and keeps them in RAM (memory). It does not create a physical folder with compiled files.
→ Speed: Any change we save is instantly transmitted to the browser via HMR (Hot Module Reloading), making the coding experience fluid.
→ Not Production Ready: Although fast, this mode is not stable, secure, or optimized to be accessed by thousands of real users on the internet.
From src to dist:
When and Why Do We Compile❓ Before publishing the application (moving to Production), we must compile the code:
→ The Command: npm run build.
→ Optimization: This command takes all our source files (React, CSS, etc.), minifies them (makes them much smaller), concatenates them (puts them into fewer bundles), and optimizes them for maximum loading speed.
→ The Result dist: All these optimized files are put into a new, physical folder, usually called dist or build. This folder contains the final, static version of the frontend, ready to be served.
Enter express.static():
→ How Do We Serve the dist Folder❓ Now that we have the optimized dist folder, we must tell Express (the backend) that this is the folder it needs to serve publicly:
→ The Solution is express.static(): This line of code is the instruction we give the Express server:
// in production, Express serves the frontend's 'dist' folder
app.use(express.static(path.join(__dirname, 'client/dist')));
→ What It Does: Whenever an HTTP request reaches the server and does not match an API route we defined (e.g. doesn't start with /api), Express looks in the dist folder! If it finds a file matching the requested URL (e.g., /style.css), it sends it immediately to the client and stops the routing process❗
Development vs Production:
→ If we have seen the command npm run dev used in both our frontend (React) and our backend (Express) project directories, we might think they do the same thing. They don't❗ This is a critical distinction, especially when thinking about security and performance.
→ The Dual npm run dev: Two Servers, Two Purposes
When we talk about Development Mode (Dev) and Production Mode (Prod), we are describing the state and purpose of our running code.
Development Mode (our workbench):
Development Mode is our workbench, it's optimized for speed and debugging.
→ Purpose: the sole goal is to facilitate rapid coding, testing, and debugging.
→ Frontend Setup: the server runs the Vite Dev Server on one port (e.g., 5173).
→ Backend Tooling: we use tools like nodemon to automatically restart the server the moment we save a change.
→ Errors: errors are loud and detailed (stack traces) and are printed directly to your console, helping you fix issues instantly.
→ Optimization: files are served as-is, with minimal optimization, as file size and speed aren't the primary concerns.
Production Mode (the live public server)
Production Mode is the final, optimized version delivered to the public, it's built for stability and security.
→ Purpose: the goal is to deliver a stable, fast, and secure experience to the public.
→ Frontend Setup: the frontend code is first built (compiled and minified) and then served by the single Express server on its designated port (e.g., 3000).
→ Backend Tooling: we use highly optimized process managers (like PM2) to keep the server running continuously and robustly.
→ Errors: errors are logged internally but are never shown to the public user, preventing security leaks❗
→ Optimization: files are heavily minified and aggressively cached for maximum performance and faster loading times for all users.
Security Risks:
Why we don't run development mode in production❓
Running your Express server in Development Mode on a live, public server is a major security flaw and is strongly discouraged.
→ Security Leaks: Development errors often include sensitive data, such as file paths on your server, environment variables, or even glimpses into your database structure. Revealing this information to an attacker is a severe vulnerability❗
→ Performance: Dev mode is built for convenience, not speed. You will experience slow response times, poor resource management, and limited scalability under real user load.
→ Stability: Tools like nodemon are meant to restart your code whenever you save a change. This behavior is unpredictable and highly unstable for a public-facing application that needs to run 24/7❗
Order Matters: Handling Critical Paths in Express
Now, let's look at the crucial process of setting up our routes and middleware in the correct order to ensure Express handles all requests correctly.
The Role of Middleware: app.use()
In Express, a Middleware is simply a function that executes during the request-response cycle. It has access to the request object req, the response object res, and the next function next in the cycle.
→ app.use(middleware): This method is primarily used to register middleware. It executes the provided function for ALL HTTP methods (GET, POST, etc.) and for ALL paths (unless we specify a path prefix).
→ The Golden Rule: Middleware executes in the exact order they are defined in our code. Once a middleware sends a response (e.g., res.send('Hello')), the cycle stops, and no subsequent middleware or route handlers are executed❗
Example Order of Flow:
app.use(cors)app.use(express.json)app.use('/api', apiRouter)-
app.use(express.static(dist_path))<- This is the critical separation point❗ - ... other routes ...
The React Router Trap:
Why We Need app.get('*')❓This is the final piece of the puzzle that links our backend logic to our frontend application.
→ The Purpose: the app.get('*') route is the Catch-All Handler. It uses the wildcard * to match any GET request that hasn't been handled by any middleware or route defined before it.
→ The Scenario: when a user types a direct link like mysite.com/profile into their browser, the server sees a request for /profile.
- Express checks the
APIroutes (e.g.,/api). No match❗ - Express checks
express.static()for a file named profile. No match.❗ - The request falls to
app.get('*').
→ The Solution: we tell Express to simply serve the main application file (index.html) for any unmatched path.
app.get('*', (req, res) => {
// we tell Express: "Send the HTML file and let React Router handle the URL."
res.sendFile(path.resolve(FRONTEND_BUILD_PATH, 'index.html'));
});
→ Result: The browser loads index.html, React takes over, and React Router reads the /profile URL and displays the correct component, making the single-page application experience seamless❗
Conclusion❤️
If you’re a beginner in backend development and these things didn’t make sense right away, don’t be disappointed, they can definitely be confusing at first. I think we’ve all had these questions when we were just starting out... I know I sure did❗ 🙃
The main challenge is that we need to shift our mindset and the way we think, from the automatic, in-memory serving of a frontend tool like Vite, to the more manual and structured routing approach of Express.
I hope this article helps you out, or maybe reminds you of the struggles you faced at the beginning of your backend journey.🤗
I’m really curious how many of us have gone through the same confusion at first, leave me a comment if backend development gave you a hard time too! I’d love to know I wasn’t the only one.🤗🥰
Happy reading, and if this article helped you or you found it useful, give it a reaction!🤗
More articles:
Node.js-Steps for building your first server❗
My First Node.js: Mastering the Fundamentals❗


Top comments (1)
This was a super clear breakdown! 🙌
Loved how you explained the shift from Vite’s “magic” to Express’s manual routing — especially the part about express.static() and app.get('*'). The dev vs prod section was spot on too. Perfect read for beginners! 🚀