Deploy a Node.js App to Azure App Service
Deploy a Node.js Express API to Azure and configure it without redeploying.
Introduction
30 Second Summary
You've built an app that works on your machine. Sharing it with the world means putting it somewhere anyone can reach, without leaving your laptop running 24/7.
In this project, you will deploy a Node.js Express API to Azure App Service, configure it from the command line without redeploying, and stream live logs to watch requests hit your app in real time.
What You'll Build
Share a URL with anyone and they'll get a JSON response from your Express API running live on Azure, configured entirely from your terminal.
By the end of this project, you'll have:
- A public URL (https://your-app-name.azurewebsites.net) serving your API with live JSON responses.
- Cloud-native configuration that you change from the CLI without touching code or redeploying.
- Real-time log streaming that shows requests hitting your live app as they happen.
- Secret Mission: Add a health check endpoint that reports app uptime and environment details, and observe it live via log streaming.
Are there any prerequisites?
You'll need an Azure account with an active subscription, plus VS Code and the Azure CLI already installed. Step 1 walks you through installing Node.js.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters. This step is your chance to commit to the goal before any hands-on work begins.
Set Up Node.js and Verify Azure Access
Your Express API needs Node.js to run JavaScript outside the browser. Your deployment commands need a confirmed connection to your Azure subscription.
In this step, you'll install Node.js and verify that your Azure CLI can reach your account. Without these two pieces, nothing else in the project works.
In this step, get ready to:
- Install Node.js LTS on your machine.
- Verify your Azure CLI is connected to an active subscription.
Install Node.js LTS
Node.js is the runtime that lets you execute JavaScript on your machine. You need version 24 or higher (the current LTS release) for this project.
- Open a terminal in VS Code by pressing Ctrl+` (macOS) or Ctrl+` (Windows).
- Check if Node.js is already installed by running:
node --version
✔️ I see v24 or higher
Node.js is ready. Skip ahead to the next substep where you verify your Azure access.
ⓧ I see an older version
You have an older version of Node.js that won't work for this project. You need to upgrade to version 24 LTS.
- Go to https://nodejs.org/en/download in your browser.
- The page auto-detects your operating system. Download the LTS installer (.msi on Windows, .pkg on macOS).
- Run the installer and accept all defaults.
- Close your VS Code terminal and reopen it with Ctrl+` so it picks up the updated PATH.
- Run node --version again. You should now see v24 or higher.
Why close and reopen the terminal?
When you install Node.js, the installer adds it to your system PATH. An already-open terminal still uses the old PATH. Closing and reopening forces it to reload.
ⓧ Command not found
Node.js isn't installed yet. Let's install it now.
- Go to https://nodejs.org/en/download in your browser.
- The page auto-detects your operating system. Download the LTS installer (.msi on Windows, .pkg on macOS).
- Run the installer and accept all defaults.
- Close your VS Code terminal and reopen it with Ctrl+` so it picks up the new PATH.
- Run node --version again. You should now see v24 or higher.
Why close and reopen the terminal?
When you install Node.js, the installer adds it to your system PATH. An already-open terminal still uses the old PATH. Closing and reopening forces it to reload.
- Confirm that npm (Node's package manager) is also available by running:
npm --version
You should see a version number printed. This confirms both Node.js and npm are ready.
npm command not found?
npm installs automatically with Node.js. If this command fails, your Node.js installation may be incomplete.
- Re-run the Node.js installer from the download page and make sure you accept all defaults.
- Close and reopen the VS Code terminal after reinstalling.
- Help me troubleshoot npm not being found
Sign in to Azure and confirm your subscription
The Azure CLI is already installed on your machine. You need to sign in so it can create and manage resources in your Azure account.
- Sign in to Azure by running:
az login
What happens when you run this?
A browser window opens and asks you to sign in with your Microsoft or Azure credentials. After you authenticate, the terminal displays a JSON list of your subscriptions.
- Complete the sign-in in your browser when the authentication page appears.
- Return to your VS Code terminal after the terminal shows your subscription list.
Before you run the next command, what do you expect to see if your Azure subscription is connected correctly?
- Confirm your subscription is active by running:
az account show
You should see JSON output with your subscription name and a state field showing Enabled. This confirms the Azure CLI can manage resources on your behalf.
Seeing 'No subscriptions found'?
- Make sure you signed in with the same Microsoft account that has an active Azure subscription.
- If you have multiple accounts, run az login again and choose the correct one in the browser.
- Help me fix my Azure CLI subscription issue
Your tools are locked and loaded. Node.js is ready to build your Express API, and the Azure CLI is connected to your subscription. Next up, you'll write the API and run it locally.
Build and Run Your Express API Locally
Your development environment is ready with Node.js and the Azure CLI connected. Now you need an actual application to deploy.
Building the app locally first gives you a working baseline. If something breaks after deployment, you'll know the issue is Azure-related, not a code bug.
In this step, get ready to:
- Create a project folder and install Express.
- Write a simple Express API in app.js.
- Run the server and confirm it returns JSON at http://localhost:3000.
Create the project folder and install Express
Every Node.js project lives in its own folder with a package.json file that tracks dependencies. You'll create that folder on your Desktop so it's easy to find.
- Move to your Desktop by running this command in the VS Code terminal:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir my-azure-app
cd my-azure-app
- Confirm you're in the new folder by running:
ls
The output should be empty since the folder is brand new.
- Initialize a new Node.js project by running:
npm init -y
You'll see output showing a new package.json file was created with default values.
- Install Express version 4.21.2 by running:
npm install express@4.21.2 --save-exact
Why a specific version?
Express 5 is now the default on npm. This project uses Express 4, which has a slightly different API. Pinning the exact version ensures your code works exactly as written in this project.
You'll see output showing packages were added to node_modules.
Seeing a network error?
Check that you're connected to the internet and try the command again. If you see a permissions error on macOS, prefix the command with sudo.
help me fix an npm install error
- Open package.json in VS Code by clicking it in the Explorer sidebar.
- Replace the entire contents with the following code:
{
"name": "my-azure-app",
"version": "1.0.0",
"description": "A Node.js Express API deployed to Azure App Service",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "4.21.2"
}
}
Why update package.json?
The "start": "node app.js" script tells Azure how to launch your app. Without it, Azure won't know which file to run.
The "main": "app.js" field points Node.js to your entry file. We also removed the auto-generated fields that aren't needed.
- Save package.json by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Make sure package.json is saved and move on to writing the API.
ⓧ I'd like to double check the full code
Your package.json should look exactly like this:
{
"name": "my-azure-app",
"version": "1.0.0",
"description": "A Node.js Express API deployed to Azure App Service",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "4.21.2"
}
}
Write the Express API
Express is a lightweight framework that handles HTTP requests. You'll create a single endpoint that returns JSON data.
- Create a new file called app.js by right-clicking in the Explorer sidebar and selecting New File.
- Name the file app.js and press Enter.
- Paste the following code into app.js:
// Import Express and create the app
const express = require("express");
const app = express();
// Use Azure's PORT env var, or 3000 for local development
const port = process.env.PORT || 3000;
// Define the root route - returns JSON with app status
app.get("/", (req, res) => {
res.json({
status: "running",
message: "Hello from my Azure App!"
});
});
// Start the server and log which port it's using
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
What does this code do?
- process.env.PORT || 3000 checks if Azure has assigned a port. Locally, no port is assigned, so it falls back to 3000.
- app.get("/", ...) defines what happens when someone visits the root URL. It sends back a JSON object with a status and a welcome message.
- app.listen(port, ...) starts the server and prints a confirmation to the terminal.
- Save app.js by pressing Cmd+S (macOS) or Ctrl+S (Windows).
✔️ Awesome, I've got everything!
Great. Make sure app.js is saved and move on to running your API.
ⓧ I'd like to double check the full code
Your app.js should look exactly like this:
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
app.get("/", (req, res) => {
res.json({
status: "running",
message: "Hello from my Azure App!"
});
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
Run and verify the API locally
Your code is written. Time to start the server and confirm it responds with JSON.
- Before you run the command below, predict what you'll see in the terminal. Will it show an error, or a success message?
- Start the server by running:
node app.js
You should see App listening on port 3000 printed in the terminal. The server is now running and waiting for requests.
- Open your browser and navigate to http://localhost:3000.
You should see a JSON response in the browser:
{
"status": "running",
"message": "Hello from my Azure App!"
}
What just happened?
Your Express server received an HTTP GET request at the / path and responded with a JSON object. This is the same response your live Azure app will return once deployed.
- Return to the VS Code terminal and press Ctrl+C to stop the server.
You should see the terminal return to the command prompt.
Not seeing JSON in the browser?
- Check that the terminal still shows App listening on port 3000. If not, the server crashed. Look at the error message above it for a clue (usually a typo in app.js).
- Make sure you typed http://localhost:3000 exactly. Typos like localhost:300 or missing http:// can cause a blank page.
- If you see Cannot find module 'express', re-run npm install express@4.21.2 --save-exact to reinstall the dependency.
help me debug my Express server
Your API is running locally and returning JSON. Next up, you'll deploy this exact app to Azure App Service so anyone on the internet can hit it.
Deploy to Azure App Service
Your Express API works perfectly on your machine. The problem is that nobody else can reach localhost:3000. To make your app accessible to anyone with a browser, you need to deploy it to a server with a public URL.
Azure App Service is a managed platform that handles server infrastructure for you. You upload your code, and Azure runs it on a public URL with no server management required. In this step, you'll create the Azure resources and push your app live.
In this step, get ready to:
- Create Azure infrastructure (a resource group and an App Service plan).
- Deploy your Express API to a public URL.
- Verify your app is accessible from the internet.
Create a resource group and App Service plan
Azure organizes cloud resources into logical containers called resource groups. You also need an App Service plan, which defines the server capacity your app runs on.
- Create a resource group in the eastus region by running this command:
az group create --name my-azure-app-rg --location eastus
You should see JSON output with "provisioningState": "Succeeded" near the bottom.
- Create an App Service plan on the Free F1 tier by running this command:
az appservice plan create --name my-azure-app-plan --resource-group my-azure-app-rg --sku F1 --is-linux
You should see JSON output containing the plan name and "sku": { "name": "F1" } confirming the Free tier.
What did you just create?
A resource group is a container that holds related Azure resources for a project. Deleting the group later removes everything inside it in one command.
An App Service plan defines the virtual machine that runs your app. The F1 (Free) tier gives you a shared Linux VM at no cost, perfect for learning.
Seeing an authorization or subscription error?
Make sure you are still signed in to Azure. Run az account show and confirm your subscription is listed. If not, run az login again.
If you see a quota error, your subscription may have hit its limit for Free tier plans. You can only have one F1 plan per region. Try a different region or delete an existing free plan.
help me fix an Azure CLI authorization or quota error when creating resources
Create the web app
The web app is the Azure resource that actually hosts and runs your code. Its name becomes part of your public URL, so it must be globally unique across all of Azure.
- Pick a unique name for your app. A good pattern is your name plus the project, like jane-my-azure-app.
- Create the web app by running this command (replace the app name with your own):
az webapp create --name [[APPNAME="your-unique-app-name"]] --resource-group my-azure-app-rg --plan my-azure-app-plan --runtime "NODE:24-lts"
Look for defaultHostName in the JSON output. This is your app's public URL.
Why does the name need to be globally unique?
Your app name becomes the subdomain of azurewebsites.net. Since DNS names are global, no two apps anywhere on Azure can share the same name.
Getting a message that the name already exists?
Someone else has already taken that name. Try a more specific name that includes your initials, a number, or a date. For example: jd-azure-app-2026.
help me pick a unique Azure web app name that isn't taken
Zip and deploy your code
Azure App Service accepts zip deployments. You package your source files into a zip, then push it to Azure where the platform installs your dependencies and starts your app.
- First, enable build automation so Azure runs npm install for you after deployment:
az webapp config appsettings set --name [[APPNAME="your-unique-app-name"]] --resource-group my-azure-app-rg --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true
Why set this before deploying?
By default, zip deployment does not run npm install. Setting SCM_DO_BUILD_DURING_DEPLOYMENT=true tells Azure to install your dependencies from package.json automatically. This means you only need to zip your source files, not the entire node_modules folder.
- Create a zip file containing app.js and package.json by running the command for your operating system:
macOS
zip app.zip app.js package.json
Windows
Compress-Archive -Path app.js,package.json -DestinationPath app.zip
- Confirm the zip file was created by running:
ls app.zip
You should see app.zip listed in the output.
- Deploy the zip to your Azure web app by running this command:
az webapp deploy --resource-group my-azure-app-rg --name [[APPNAME="your-unique-app-name"]] --src-path app.zip --type zip
This command uploads your zip and triggers a build. It may take a minute or two to complete.
Before you open your app's URL, what do you think you'll see in the browser? Will it be the same JSON you saw locally, or something different?
- Open your app's public URL in a browser. The URL follows this pattern:
https://[[APPNAME="your-unique-app-name"]].azurewebsites.net
You should see the same JSON response you saw locally: {"status":"running","message":"Hello from my Azure App!"}. Your app is now live on the internet.
Seeing a default Azure page or an error?
If you see a default Azure welcome page, the deployment may still be building. Wait 2-3 minutes and refresh.
If you see an application error, check that SCM_DO_BUILD_DURING_DEPLOYMENT was set to true before you deployed. You can verify by running:
az webapp config appsettings list --name [[APPNAME="your-unique-app-name"]] --resource-group my-azure-app-rg
If the setting is missing, set it and redeploy by running the az webapp deploy command again.
help me troubleshoot my Azure App Service deployment that shows an error page
Your API is live on the internet. Anyone with the URL can hit your endpoint and see the response. Next up, you'll discover why hardcoding configuration is a problem and solve it with Azure App Settings.
Configure with Azure App Settings
Your app is live, but the welcome message is hardcoded directly in your source code. Right now, changing that message requires editing app.js, creating a new zip, and redeploying the entire app.
That is the wrong pattern for cloud apps. Azure App Settings injects environment variables into your running app at runtime. You can change configuration without touching code or redeploying.
In this step, get ready to:
- Update app.js to read configuration from an environment variable.
- Set an App Setting via the Azure CLI.
- Confirm the message changes without redeploying any code.
Update app.js to read from an environment variable
Your app currently returns a hardcoded string. To make it configurable from the cloud, the code needs to read from process.env.WELCOME_MESSAGE with a fallback to the original default.
- In VS Code, open app.js in your my-azure-app folder.
- Find these lines near the top of the file:
const port = process.env.PORT || 3000;
- Add the following line directly below it:
const welcomeMessage = process.env.WELCOME_MESSAGE || "Hello from my Azure App!";
What does this line do?
This reads the WELCOME_MESSAGE environment variable at startup. If the variable is not set, it falls back to the original hardcoded string.
This means your app works the same as before locally, but in the cloud you can override the message without changing the code.
- Now find the GET / route handler. Replace the entire app.get block with this updated version:
app.get("/", (req, res) => {
console.log(`${new Date().toISOString()} GET /`);
res.json({
status: "running",
message: welcomeMessage
});
});
What changed in the route handler?
- console.log(...) logs a timestamp and the request path each time someone hits your API. This becomes useful for log streaming later.
- message: welcomeMessage now uses the variable you defined above instead of the hardcoded string.
- Save app.js.
- Verify the app still works locally by running this command in your terminal:
node app.js
- Open http://localhost:3000 in your browser. You should see the same JSON response as before with the message Hello from my Azure App! since no environment variable is set locally.
- Press Ctrl+C in the terminal to stop the server.
Seeing a syntax error?
- Check that the backtick characters in console.log(...) are actual backticks (the key left of 1 on your keyboard), not single quotes.
- Make sure you have a closing }); for both the app.get block and the res.json call.
Help me debug a syntax error in my Express app.js file
✔️ Awesome, I've got everything!
Great. Make sure you have saved app.js before moving on.
ⓧ I'd like to double check the full code
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
const welcomeMessage = process.env.WELCOME_MESSAGE || "Hello from my Azure App!";
app.get("/", (req, res) => {
console.log(`${new Date().toISOString()} GET /`);
res.json({
status: "running",
message: welcomeMessage
});
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});
Redeploy to Azure
Your code now reads from an environment variable, but the live app still has the old version. You need to rezip and redeploy so the cloud picks up the new code.
- Create a fresh zip of your project files by running this command:
Compress-Archive -Path app.js,package.json -DestinationPath app.zip -Force
- Deploy the updated zip to your web app by running:
az webapp deploy --resource-group my-azure-app-rg --name [[APPNAME="your-unique-app-name"]] --src-path app.zip --type zip
- Wait about 30 seconds for the deployment to complete, then refresh your app's public URL in the browser.
- You should see the same JSON response with Hello from my Azure App! as the message. The fallback is working because you have not set the environment variable yet.
Deployment command failing?
- Make sure your terminal is still in the my-azure-app folder where app.zip was created.
- Verify the app name matches exactly what you used when you created the web app in the previous step.
Help me troubleshoot an az webapp deploy failure
Set an App Setting and watch it take effect
Here is where the cloud-native pattern pays off. You are about to change what your app returns without editing any code or redeploying.
- Before you run the next command, predict: will the app restart automatically when you change the setting, or will you need to restart it manually?
- Set the WELCOME_MESSAGE App Setting by running:
az webapp config appsettings set --name [[APPNAME="your-unique-app-name"]] --resource-group my-azure-app-rg --settings WELCOME_MESSAGE="Hello from Azure App Settings!"
What does this command do?
This sets an environment variable on your running Azure web app. When the app starts (or restarts), process.env.WELCOME_MESSAGE picks up the value you set here.
Azure App Service automatically restarts the app when App Settings change. No manual restart needed.
- Wait about 30 seconds for the app to restart, then refresh your app's public URL in the browser.
You should see the message has changed to Hello from Azure App Settings! in the JSON response. You did not edit code. You did not redeploy. The app read the new value from its environment on restart.
Why does this matter?
In production, configuration values like database connection strings, feature flags, and API URLs all change independently of code. App Settings lets you manage these without a deployment cycle.
This pattern is called separating configuration from code. It is one of the core principles of cloud-native applications.
Still seeing the old message?
- App Service can take up to a minute to restart after a settings change. Wait a bit longer and try a hard refresh (Ctrl+Shift+R).
- Make sure you updated app.js to read from process.env.WELCOME_MESSAGE AND redeployed before setting the App Setting. If the old code is still running, it ignores the environment variable.
Help me figure out why my Azure App Setting is not showing up
Your app now reads configuration from the cloud environment instead of hardcoded values. Next up, you will stream live logs from your running app so you can see requests hitting it in real time.
Secret mission
Stream Live Logs and Add a Health Check
Your app is live, but you're flying blind. If something breaks, you have no way to see what's happening. In this secret mission, add a health check endpoint that reports uptime and environment details, then configure real-time log streaming to observe every request as it hits your cloud app.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses the Free F1 tier, so there are no ongoing costs while your app stays live.
Resources you used:
- Azure resource group: my-azure-app-rg (contains the App Service plan and web app).
- Azure App Service plan: my-azure-app-plan (F1 Free tier).
- Azure web app: your unique app name running on the Free plan.
- Local project folder: my-azure-app on your Desktop.
Keep everything running
No action needed. Choose this if you are still actively building or want to keep your API live.
- The F1 Free tier incurs no charges. Your app stays live indefinitely at no cost.
- The Free tier has a daily CPU quota (60 CPU minutes per day). If exceeded, your app returns HTTP 403 until the quota resets at midnight UTC.
- To restart the app later if it was stopped, run az webapp start with your app name and resource group.
Pause - I'll come back to this later
Stop the running web app to free up compute resources on the shared Free tier. Your code, settings, and resource group remain intact so you can restart any time.
- Stop your web app by running this command:
az webapp stop --name [[APPNAME="your-unique-app-name"]] --resource-group my-azure-app-rg
- Your app URL will return an HTTP error while stopped, but all configuration and code remain saved.
- When you are ready to resume, run:
az webapp start --name [[APPNAME="your-unique-app-name"]] --resource-group my-azure-app-rg
- Your local my-azure-app folder stays on your Desktop for future edits.
Delete - I don't want to use this again
Remove all Azure resources and local project files. This cannot be undone.
- Delete the entire resource group (this removes the web app, App Service plan, and all settings in one command) by running:
az group delete --name my-azure-app-rg --yes --no-wait
- Delete your local project folder. On macOS or Linux, run:
cd ~/Desktop
rm -rf my-azure-app
- On Windows PowerShell, run:
cd ~/Desktop
Remove-Item -Recurse -Force my-azure-app
- Your Azure subscription remains active. Only the resources inside my-azure-app-rg are removed.
That's a Wrap!
That's a Wrap!
Nice work! You've just built and deployed a live REST API to Azure App Service, configured it from the command line, and watched real-time traffic flow through it.
You've learned how to:
- Built a Node.js Express API from scratch and deployed it to Azure App Service using the current recommended CLI commands (az webapp create + az webapp deploy).
- Configured cloud-native environment variables (App Settings) to change app behavior without redeploying.
- Streamed live logs from your running cloud app to your local terminal for real-time monitoring.
- Secret Mission: Added a health check endpoint reporting uptime and Node version, and observed it live via log streaming.
Ready to quiz yourself?