Going ProfessionalExtra· 45 min read

Deployment & Project Structure

A back-end on localhost helps nobody. Organise your code like a professional and put your API live on the internet.

What you will learn

  • Organise an Express app into routes, controllers and models (MVC)
  • Prepare an app for production (NODE_ENV, env vars, start script)
  • Deploy to a hosting platform and use a Git/GitHub workflow

First, organise the code (MVC)

Every example so far lived in one file — fine for learning, but a real API with auth, validation and a dozen routes becomes an unreadable wall of code. Professionals split an Express app by responsibility, a pattern called MVC (Model–View–Controller). For an API there is no visual "View", so you focus on three folders:

FolderHoldsJob
models/Data schemas (e.g. Task.js)Defines the shape of your data and talks to the database
routes/Route definitions (URLs + methods)Maps each URL/method to a controller function
controllers/The handler logicContains what actually happens for each route

So instead of one giant file, a request flows: routes/ matches the URL, calls a function in controllers/, which uses a models/ object to read or write the database. Here is the same /tasks logic, split up:

One feature split into controller + route + model
// controllers/taskController.js — the logic
const Task = require("../models/Task");

exports.getTasks = async (req, res) => {
  const tasks = await Task.find();
  res.json(tasks);
};

// routes/taskRoutes.js — the URL map
const express = require("express");
const router = express.Router();
const { getTasks } = require("../controllers/taskController");

router.get("/", getTasks);
module.exports = router;

// app.js — wire the router in under a base path
app.use("/tasks", require("./routes/taskRoutes"));

Reading it: the controller holds the actual work (getTasks fetches and responds). The router uses Express’s Router() — a mini-app for one resource — to say "a GET on this router runs getTasks". Finally app.use("/tasks", router) mounts the whole router under /tasks, so router.get("/") becomes GET /tasks. The single-file logic is unchanged; it is just sorted into labelled drawers, which makes a growing app navigable.

Prepare for production

Code that works on your laptop needs a few tweaks before it can run on a real server. The three essentials:

  1. Read the port from the environment — hosts assign one for you: const PORT = process.env.PORT || 3000;. Never hard-code it.
  2. Keep every secret (database URL, JWT_SECRET) in environment variables, set in the host’s dashboard — never committed to git.
  3. Add a "start": "node server.js" script — hosting platforms run npm start to launch your app.

Many libraries also behave differently in production based on NODE_ENV — a standard variable that is set to "production" on the live server. You can use it to, say, hide detailed error messages from users:

Hide internal errors in production with NODE_ENV
// Show full errors only while developing
app.use((err, req, res, next) => {
  console.error(err);   // always log the real error for yourself
  const isDev = process.env.NODE_ENV !== "production";
  res.status(500).json({
    error: isDev ? err.message : "Something went wrong"
  });
});

Note: Output (in production, when a route throws): {"error":"Something went wrong"} Output (in development): {"error":"Cannot read properties of undefined (reading 'name')"} You (the developer) always get the real error in your server logs via console.error, while end users in production only see a safe, generic message — leaking internal details would help attackers.

Tip: For real logging in production, console.log is not enough. Libraries like Morgan (logs every HTTP request) or Winston/Pino (structured logs you can search and store) give you proper, timestamped records of what your server did — invaluable when debugging a live problem.

Put it live

Hosting platforms like Render, Railway or Fly.io run your Node app on a public server and give it a URL. The modern flow connects them to GitHub, so deploying becomes simply "push your code". The typical process:

  1. Put your project on GitHub (init a repo, commit, push) — and make sure node_modules/ and .env are in .gitignore so they are never uploaded.
  2. Create a new web service on the host and connect your GitHub repo.
  3. Set the start command to npm start and add your environment variables (database URL, JWT_SECRET, etc.) in the host’s dashboard.
  4. The host installs dependencies, starts your app, and gives you a public URL like https://my-api.onrender.com.
  5. From now on, every git push to your main branch triggers an automatic re-deploy — this push-to-deploy automation is the simplest form of CI/CD (Continuous Integration / Continuous Deployment).

Note: Output (after deploy): Your API is reachable at a public URL, e.g. https://my-api.onrender.com/tasks — anyone, including your React app or a recruiter, can call it. It is no longer trapped on localhost.

Watch out: Never commit your .env file or push secrets to GitHub. Anything pushed to a public repo can be scraped by bots within minutes. Set secrets in the host’s environment settings instead, and rotate any key that slips out.

Tip: Point your deployed React front-end’s fetch calls at this new public API URL (kept in a front-end env variable), and you have a complete, live, full-stack app you can put on your résumé.

Q. Why read the port with process.env.PORT instead of hard-coding 3000?

Answer: Production hosts set the port through an environment variable. Reading process.env.PORT (with a local fallback) lets the same code run on your laptop and on the live server unchanged.

✍️ Practice

  1. Refactor a single-file API into models/, routes/ and controllers/ folders, keeping the behaviour identical.
  2. Make the app production-ready: read process.env.PORT, add a start script, and hide error details when NODE_ENV is "production".

🏠 Homework

  1. Push an Express API to GitHub (with a proper .gitignore) and deploy it to a free host like Render, then share the public URL of a working endpoint.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →