Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services
Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services

Blog Posts

Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services
Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services
Back to all blogs

How to Deploy a Full-Stack Application

Jun 14, 2026•8 min read

Deploying a full-stack app comes down to five moving parts in the right order: get your environment production-ready, deploy the database, deploy the backend, deploy the frontend, then connect them over HTTPS with correct CORS. Do it in that sequence and each step has what it needs from the one before. Below is the exact process I follow, with a MERN example you can copy.

Step 1: Prepare for production

Most deploy failures trace back to work skipped here. Before touching a host, get three things right.

No hardcoded secrets. Every URL, key, and credential moves to environment variables. If a database password is sitting in your source, it's already compromised the moment the repo is shared. Use a .env file locally (gitignored) and your host's secret manager in production.

bash
1# .env.local — never commit this 2DATABASE_URL="mongodb+srv://user:pass@cluster.mongodb.net/app" 3JWT_SECRET="a-long-random-string" 4NODE_ENV="production"

A real production build. Development servers are not production servers. Run and test the actual build locally first — it'll catch missing env vars and build-time errors before your host does.

bash
1npm run build # produces the optimized bundle 2npm start # run the production build locally to smoke-test it

A production checklist. I keep a short one and run it every time:

  • Secrets in environment variables, not code
  • .env files gitignored
  • Production build succeeds locally
  • Database connection string points to the managed DB
  • CORS configured for the real frontend origin
  • Error logging enabled

Step 2: Choose your hosting

There's no single right answer, but the modern default splits into three layers. Here's what I reach for:

LayerOptionsI usually pick
FrontendVercel, Netlify, Cloudflare PagesVercel (esp. for Next.js)
BackendRailway, Render, Fly.io, AWSRailway or Render for speed; AWS for control
DatabaseManaged Postgres, MongoDB AtlasAtlas (Mongo) / Neon or RDS (Postgres)

The principle: use managed services for the database, always. Running your own database on a raw VM means you own backups, failover, patching, and scaling. A managed provider does all of that for a few dollars a month. It's the single best money-for-time trade in deployment.

Step 3: Deploy the database first

The database has no dependencies, so it goes first — everything else needs its connection string.

For MongoDB Atlas: create a cluster, add a database user, and configure network access. In production you'd lock this to your backend's IP; while getting started, 0.0.0.0/0 works but tighten it before you have real data. Grab the connection string — that becomes your backend's DATABASE_URL.

Step 4: Deploy the backend

Point your backend host (Railway/Render/Fly) at your repo, set the environment variables — including the database URL from Step 3 — and let it build. Two things that trip people up:

  • Bind to the host's port. Cloud hosts inject a PORT env var; hardcoding 3000 will fail.
  • Set NODE_ENV=production.
js
1const port = process.env.PORT || 3000; 2app.listen(port, () => console.log(`API listening on ${port}`));

Once it's live, hit a health endpoint (GET /api/health) to confirm the backend and database are talking before you move on.

Step 5: Deploy the frontend

Connect your frontend repo to Vercel/Netlify. The one critical env var here is the backend's public URL, so the frontend knows where to send API calls:

bash
1# Frontend environment variable 2VITE_API_URL="https://my-api.up.railway.app" 3# or for Next.js 4NEXT_PUBLIC_API_URL="https://my-api.up.railway.app"

Step 6: Connect them and fix CORS

This is where a first deploy usually breaks: the frontend loads, but every API call fails with a CORS error. Your backend must explicitly allow requests from the frontend's origin.

js
1import cors from "cors"; 2 3app.use(cors({ 4 origin: process.env.FRONTEND_URL, // e.g. https://myapp.vercel.app 5 credentials: true, // needed if you send cookies 6}));

Don't ship origin: "*" with credentials — browsers reject it, and it's a security hole. Name the exact origin.

Step 7: Custom domain and HTTPS

Point your domain's DNS at your frontend host (a CNAME or the host's provided records). Every host in this stack provisions a free TLS certificate automatically, so HTTPS is handled — you just wait for DNS to propagate. Do the same for your API subdomain (e.g. api.myapp.com) and update the frontend's API_URL to match.

Step 8: Test end-to-end

Load the live site and exercise the real flows: sign up, log in, create data, refresh. Open the browser network tab and confirm API calls hit the production backend over HTTPS with no CORS errors. Check your host's logs for runtime errors.

The full MERN picture

Here's how the pieces connect in a typical MERN deploy — Vercel frontend, Railway backend, MongoDB Atlas:

                    ┌─────────────────┐
   User's browser ──HTTPS──▶  Vercel   │  React/Vite frontend
                    │  (myapp.com)     │  env: NEXT_PUBLIC_API_URL
                    └────────┬─────────┘
                             │ fetch() over HTTPS (CORS-allowed origin)
                             ▼
                    ┌─────────────────┐
                    │    Railway      │  Node/Express API
                    │ (api.myapp.com) │  env: DATABASE_URL, JWT_SECRET
                    └────────┬─────────┘
                             │ MongoDB connection string (TLS)
                             ▼
                    ┌─────────────────┐
                    │  MongoDB Atlas  │  managed database
                    │  (cluster)      │  network access locked to API
                    └─────────────────┘

Common first-deploy failures

A few problems account for the vast majority of "it works locally but not in production" tickets. Knowing them in advance saves hours:

SymptomUsual causeFix
CORS error on every API callBackend doesn't allow the frontend originSet origin to the exact frontend URL
Backend crashes on bootHardcoded port; missing env varUse process.env.PORT; set all env vars on the host
API calls 404 or hit localhostFrontend API_URL still points at devSet the production API URL as a build-time env var
Database connection refusedNetwork access not allowing the backendAllow the backend's IP in the DB's network rules
Works, then 500s under loadNo connection pooling / cold DBUse a pooled client; keep the DB warm
Secrets exposed.env committed to the repoGitignore it, rotate the leaked secret immediately

The theme is consistent: production differs from local in exactly the places you took shortcuts — ports, origins, URLs, and secrets. Get those into environment variables and most of this disappears.

Automate it next

Deploying once by hand is a great way to learn. Deploying by hand every time is how mistakes happen. The natural next step is a pipeline that runs your tests and deploys automatically on every merge — I cover that in How to Set Up CI/CD for a Web Application.

This article is part of my larger guide on Deploying, Scaling & Architecting Full-Stack Apps, which covers where deployment fits alongside performance and architecture.

Related Posts

Deploying, Scaling & Architecting Full-Stack Apps

A practical map for taking a full-stack app from a working codebase to a production system that ships reliably and scales — deployment, CI/CD, performance, and architecture choices.

How to Set Up CI/CD for a Web Application

Build a CI/CD pipeline that makes deploys boring and safe — the full flow from git push to production, a working GitHub Actions YAML file, deployment strategies, and the best practices that keep pipelines fast and reliable.

How to Optimize Web Application Performance

A priority-ordered playbook for making web apps fast — measure Core Web Vitals first, then reduce JavaScript, optimize images, fix caching, tune the backend, and cut re-renders. Based on real high-traffic optimization work.