Deploying a TypeScript Express.js app on a VPS
TypeScript adds an extra compilation step to your Express deployments. You can’t just run node index.js right out of the repo. You need to build the project, manage the background process, and set up a reverse proxy.
1. Prepare your application
Make sure your build scripts actually output to the right place before you push to the server.
TypeScript configuration
Check that your tsconfig.json outputs the compiled code to a specific directory (like dist):
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Package.json scripts
You need standard build and start scripts:
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node-dev src/index.ts"
}
}
2. Server setup
SSH into your server and install Node.js. The NodeSource setup script is the easiest way to get the latest LTS version:
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
3. Clone and build your app
Create a directory, clone your repo, and compile the code:
sudo mkdir -p /var/www/your-express-app
sudo chown -R $USER:$USER /var/www/your-express-app
cd /var/www/your-express-app
git clone your-repository-url .
npm install
npm run build
Environment setup
Set up your environment variables. It’s important to set NODE_ENV to production so Express knows to cache views and drop verbose error messages.
cp .env.example .env
nano .env
4. Install and configure PM2
Node is single-threaded and crashes if an unhandled exception occurs. PM2 restarts your app automatically if it goes down.
Install it globally:
sudo npm install -g pm2
Start your compiled app:
pm2 start dist/index.js --name "your-app-name"
Tell PM2 to boot up automatically when the server restarts:
pm2 startup
pm2 save
(If you ever need to check on things, pm2 logs and pm2 monit are your best friends).
5. Configure Nginx as a reverse proxy
You shouldn’t expose your Node app directly to port 80. Use Nginx to handle the incoming connections and pass them to Express.
Create a new Nginx config file:
sudo nano /etc/nginx/sites-available/your-express-app
Add this configuration (adjust the port if your app doesn’t use 3000):
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Enable it and restart Nginx:
sudo ln -s /etc/nginx/sites-available/your-express-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
6. Automate updates
Write a quick bash script so you don’t have to remember these steps every time you deploy.
nano /var/www/your-express-app/deploy.sh
#!/bin/bash
cd /var/www/your-express-app
git pull
npm install
npm run build
pm2 restart your-app-name --update-env
Make it executable:
chmod +x deploy.sh
Now you just run ./deploy.sh to push updates live.
Production checklist
Before you call it done, double-check a few things:
NODE_ENVis actually set toproduction.- You aren’t shipping source maps to production unless you have a specific reason to.
- You have some form of rate limiting (like
express-rate-limit). - You’ve set up security headers (like
helmet).