Anupam Baral/blog
Web Development & Cloud

From Localhost to Production: How to Deploy a Full-Stack Web App for Free in 2026

React + Node.js + PostgreSQL + GitHub + Free Cloud Hosting...

Anupam BaralAnupam Baral
··10 min read
From Localhost to Production: How to Deploy a Full-Stack Web App for Free in 2026
Figure 1: Full-Stack Deployment Workflow
Figure 1: Full-Stack Deployment Workflow

React + Node.js + PostgreSQL + GitHub + Free Cloud Hosting

You finally finished your full-stack project.

The frontend works.

The API works.

The database works.

Everything is beautiful on:

localhost Then someone asks:

“Can I access it from the internet?”

And suddenly your perfectly working application has a completely different problem.

Your frontend is running on one machine. Your backend expects localhost. Your database is probably running locally. Your environment variables contain development URLs. CORS starts complaining. Your production server doesn't know which port to use.

And you realize something important:

Building a full-stack application and deploying a full-stack application are two completely different skills.

The good news?

You don’t necessarily need to pay for a VPS to put your project online.

With modern cloud platforms and their free tiers, developers can deploy many personal projects, portfolios, prototypes, and small applications for $0 .

This guide explains how to take a typical:

React → Node.js/Express → PostgreSQL

application from localhost to production .

What You’ll Build

By the end, your architecture will look roughly like this:

INTERNET │ ▼ ┌──────────────┐ │ Browser │ └──────┬───────┘ │ ▼ ┌─────────────────┐ │ Frontend │ │ React / Next.js │ └────────┬────────┘ │ HTTPS/API │ ▼ ┌─────────────────┐ │ Backend │ │ Node.js/Express │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ PostgreSQL │ │ Database │ └─────────────────┘ A possible hosting arrangement is:

GitHub │ ├── Frontend ──→ Vercel / Netlify │ ├── Backend ───→ Render / similar free-tier host │ └── Database ──→ Supabase / Neon The providers aren’t the important part.

The architecture is.

1. First Understand What “Deployment” Actually Means

Before touching a hosting dashboard, understand the difference between development and production.

During development, you might have:

Frontend http://localhost:5173 ↓ Backend http://localhost:5000 ↓ PostgreSQL localhost:5432 Everything exists on your computer.

After deployment, those services become independent cloud resources:

Frontend https://myapp.example ↓ Backend https://api.myapp.example ↓ PostgreSQL Cloud Database This creates several new concerns:

  • Where is the database?

  • How does the frontend find the backend?

  • How does the backend find the database?

  • Which origins are allowed?

  • Where are secrets stored?

  • Which port does the server use?

  • How are database migrations applied?

  • What happens when the server crashes?

That’s why deployment isn’t simply:

“Upload my project.”

It’s:

“Reconfigure my application to operate in a distributed production environment.”

2. Deploy the Database First

This is one of the simplest decisions that makes the rest of deployment easier.

Your backend needs a production database.

For local development:

DATABASE_URL="postgresql://localhost:5432/myapp" For production:

DATABASE_URL="postgresql://username:password@host:5432/database" The actual URL will come from your cloud PostgreSQL provider.

Why database first?

Because your backend depends on it.

The deployment sequence becomes:

PostgreSQL ↓ Backend API ↓ Frontend Instead of deploying everything at once and debugging three systems simultaneously.

3. Database Migrations Matter

If you’re using Prisma, your development workflow might look like:

npx prisma migrate dev But don’t blindly use development commands against production.

For applying already-created migrations in production, use:

npx prisma migrate deploy Your workflow should look like:

Developer │ ▼ Create migration │ ▼ Commit migration to Git │ ▼ Deploy application │ ▼ Apply production migration The principle is simple:

Development creates migrations. Production applies migrations.

That distinction becomes increasingly important as your application grows.

4. Deploy the Backend API

Now take your Node.js/Express backend and deploy it.

A typical project might contain:

backend/ ├── src/ │ ├── routes/ │ ├── controllers/ │ ├── services/ │ └── server.ts ├── package.json ├── tsconfig.json └── prisma/ Your production backend may need environment variables such as:

PORT=10000 DATABASE_URL=... FRONTEND_URL=https://your-frontend.example JWT_SECRET=... The exact variables depend on your application.

The Port Problem

One common beginner mistake is hardcoding a development port.

For example:

app.listen(5000); A cloud platform may provide its own port through an environment variable.

A safer pattern is:

const PORT = process.env.PORT || 5000; app.listen(PORT, "0.0.0.0", () => { console.log(`Server running on port ${PORT}`); }); Now:

Production PORT = platform-provided port Development PORT = 5000 Same code.

Different environment.

5. Your Backend Needs a Health Endpoint

Before connecting the frontend, make sure the backend actually works.

A simple health endpoint is extremely useful:

app.get("/health", (_req, res) => { res.status(200).json({ status: "ok" }); }); Then visit:

https://your-api.example/health You should get something like:

{ "status": "ok" } This gives you a very important debugging boundary:

Browser │ ▼ Frontend ❓ │ ▼ Backend ❓ │ ▼ Database ❓ If /health works, your backend deployment is alive.

If database-dependent endpoints fail, investigate the database separately.

6. CORS: The Problem That Appears After Deployment

Locally, your application might look like:

localhost:5173 │ ▼ localhost:5000 After deployment:

https://frontend.example │ ▼ https://api.example Those are different origins.

Your backend therefore needs to know which frontend is allowed to communicate with it.

For Express:

import cors from "cors"; app.use( cors({ origin: process.env.FRONTEND_URL }) ); Then:

FRONTEND_URL=https://frontend.example A common shortcut is:

cors({ origin: "*" }); That may make debugging easier, but don’t confuse “it works” with “it’s correctly configured.”

For applications involving authentication or sensitive operations, explicitly defining trusted origins is the better production habit.

7. Deploy the Frontend

Now deploy your React, Next.js, or other frontend application.

Here’s another classic deployment mistake.

Your local environment might contain:

VITE_API_URL=http://localhost:5000 You deploy the frontend.

The website loads.

But clicking Login , Register , or Load Dashboard fails.

Why?

Because the browser is still trying to call:

localhost:5000 Your user’s computer does not have your backend running there.

Change it to the production API:

VITE_API_URL=https://api.example.com For Next.js, you might have:

NEXT_PUBLIC_API_URL=https://api.example.com The exact variable name depends on your framework.

8. Never Put Secrets in the Frontend

This deserves special attention.

A frontend environment variable is not automatically secret .

For example:

VITE_API_URL=https://api.example.com That’s fine.

But this is dangerous:

VITE_DATABASE_PASSWORD=super-secret-password Why?

Because frontend code ultimately runs in the user’s browser.

If the browser can access it, an attacker may be able to inspect it.

Keep these on the backend:

DATABASE_URL JWT_SECRET PRIVATE_API_KEYS SMTP_PASSWORD SERVICE_ROLE_KEYS Frontend can contain things like: PUBLIC_API_URL PUBLIC_ANALYTICS_ID PUBLIC_APP_NAME A good rule:

If revealing the value would compromise your system, it does not belong in frontend code.

9. Environment Variables Are the Bridge Between Environments

One of the most useful deployment patterns is keeping code the same while configuration changes.

Development:

DATABASE_URL=local-database FRONTEND_URL=http://localhost:5173 Production:

DATABASE_URL=cloud-database FRONTEND_URL=https://frontend.example Your application code doesn’t need to know whether it is running locally or in the cloud.

The environment provides the configuration.

A good repository should therefore contain:

.env .env.example but only commit:

.env.example Example:

DATABASE_URL= JWT_SECRET= FRONTEND_URL= And add:

.env .env.* !.env.example 10. Connect the Three Layers At this point, you should be able to trace the entire request.

Imagine a user clicks:

“View Transactions”

The flow is:

┌───────────────┐ │ Browser │ └───────┬───────┘ │ │ GET /transactions ▼ ┌────────────────┐ │ Frontend │ └───────┬────────┘ │ │ HTTPS ▼ ┌────────────────┐ │ Backend / API │ └───────┬────────┘ │ │ SQL / Prisma ▼ ┌────────────────┐ │ PostgreSQL │ └───────┬────────┘ │ │ Data ▼ ┌────────────────┐ │ Backend / API │ └───────┬────────┘ │ │ JSON ▼ ┌────────────────┐ │ Frontend │ └───────┬────────┘ │ ▼ USER If you understand this flow, debugging becomes much easier.

11. Debug Production Like an Engineer

When something breaks, don’t randomly change settings.

Find the failing layer.

Frontend doesn’t load?

Check:

Build Deployment Environment variables Routes Browser console Frontend loads but API requests fail? Check:

API URL CORS HTTPS Network tab Backend availability Backend loads but database requests fail? Check:

DATABASE_URL Database availability SSL Migrations Credentials Connection limits This gives you a simple debugging framework:

UI problem? ↓ Frontend API problem? ↓ Backend Data problem? ↓ Database Don’t debug all three at once.

12. The Most Common Deployment Errors

localhost in production

Search your codebase:

localhost 127.0.0.1 You may discover development URLs that accidentally made it into production.

CORS error

Usually means:

Frontend origin ≠ Allowed backend origin Check your CORS configuration.

Database connection failed

Check:

DATABASE_URL Credentials SSL configuration Database status Connection limits Backend crashes immediately Check:

Build command Start command Node version Environment variables Port Deployment logs React route returns 404 If:

/dashboard /profile /settings work through client-side navigation but fail when refreshed, you may have an SPA routing/fallback configuration problem.

13. A $0 Full-Stack Deployment Stack

A practical free-tier architecture can look like:

GitHub │ ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ Frontend Backend Database Vercel Render Supabase /Netlify /etc. /Neon For a typical learning project:

React ↓ Vercel / Netlify ↓ Node.js + Express ↓ Render / similar platform ↓ PostgreSQL ↓ Supabase / Neon But don’t become dependent on one provider.

The better skill is knowing what each service does.

LayerResponsibilityGitHubSource controlFrontend hostBuild and serve UIBackend hostRun API/serverPostgreSQL providerStore application dataDNSConnect domainsEnvironment variablesConfigure environmentsCI/CDAutomate deployment

Providers change.

Architecture doesn’t.

14. Free Hosting Has a Catch

Let’s kill one misconception:

Free hosting does not mean unlimited hosting.

Free tiers can have restrictions involving:

  • CPU

  • RAM

  • bandwidth

  • storage

  • build minutes

  • database size

  • connection limits

  • request limits

  • cold starts

  • service sleep/idle behavior

Therefore, don’t write:

“You can host any production application for free forever.”

That’s misleading.

A better statement is:

Free tiers are excellent for learning, prototypes, portfolios, personal projects, and small applications — but you need to understand their limits.

And because providers change their pricing and limits, verify the current free-tier terms before publishing a provider-specific recommendation.

15. Deployed Does NOT Mean Production-Ready

This is the distinction many tutorials completely miss.

You can have:

https://my-awesome-app.com and still have a terrible production system.

Before calling your application production-ready, check:

☑ HTTPS enabled ☑ Production database configured ☑ Environment variables configured ☑ Secrets removed from source code ☑ CORS configured ☑ Authentication tested ☑ Database migrations tested ☑ Error handling implemented ☑ Logging available ☑ Health endpoint available ☑ Rate limiting considered ☑ Production build tested ☑ API tested independently ☑ Database backup strategy considered Remember:

URL │ ▼ DEPLOYED │ │ X │ ▼ PRODUCTION-READY They are not the same thing.

16. The Deployment Checklist

Before sharing your project publicly, run through this:

Frontend

□ Production API URL configured □ No localhost URLs □ Production build succeeds □ Routes work after refresh □ Environment variables checked Backend □ Production server starts □ Correct PORT configuration □ Listens on the correct host □ CORS configured □ Health endpoint works □ Production logs checked Database □ Production PostgreSQL created □ DATABASE_URL configured □ Migrations applied □ Connection tested □ Backup strategy considered Security □ .env isn't committed □ Secrets aren't in frontend □ CORS isn't unnecessarily open □ Authentication tested □ HTTPS enabled 17. The Mental Model You Should Actually Remember Don’t memorize:

“Use Provider A for frontend, Provider B for backend, Provider C for database.”

Instead remember:

USER │ ▼ FRONTEND │ │ HTTPS ▼ BACKEND │ │ DATABASE CONNECTION ▼ DATABASE Then surround that architecture with:

Environment Variables Authentication CORS HTTPS Logging Monitoring Backups CI/CD Once you understand this model, switching hosting providers becomes much less intimidating.

Final Thoughts

Your first deployment will probably fail.

That’s normal.

You might get a CORS error.

Your backend might crash because PORT isn't configured correctly.

Your frontend might still call localhost.

Your database migration might not have been applied.

You might even spend an hour debugging something that turns out to be a single environment variable.

That’s not wasted time.

That’s deployment experience.

The goal isn’t to memorize a particular hosting platform.

The goal is to understand what happens when your application leaves your laptop and becomes a distributed system.

The journey is:

LOCAL DEVELOPMENT │ ▼ DATABASE │ ▼ BACKEND API │ ▼ FRONTEND │ ▼ ENVIRONMENT CONFIGURATION │ ▼ CORS + HTTPS │ ▼ PRODUCTION TESTING │ ▼ USERS And the most important lesson is this:

Deployment isn’t about putting your code on the internet. It’s about making every layer of your application communicate reliably outside your development machine.

Once you understand that, localhost is no longer the finish line.

It’s just where production begins.

Quick Reference

Frontend ↓ React / Next.js ↓ Vercel / Netlify Backend ↓ Node.js / Express ↓ Render / similar host Database ↓ PostgreSQL ↓ Supabase / Neon Source Control ↓ GitHub Configuration ↓ Environment Variables Communication ↓ HTTPS + CORS Database Changes ↓ Production Migrations Verification ↓ Health Checks + Logs Build it locally. Understand the architecture. Deploy each layer. Connect them securely. Test every boundary.

That’s how you go from localhost to production .