NodeJs Azure Web App continuous delivery via Github and some Kudu magic

This post is about how to configure the continuous deployment settings for your NodeJs application in a Web App using Kudu.

You will learn how to configure your Azure Web App to automate deployments from Github and how to properly configure your environment via Kudu, to successfully run your application.

Before proceeding, these are the links to the repository and the deployed application.
The application repository that I am going to deploy to the Azure Web App:
https://github.com/gdyrrahitis/angular-101

The application deployed on Azure:
https://angular-101.azurewebsites.net/

Little bit about the application

This is a simple NodeJs server, that hosts an Angular client application. The Angular client contains many code samples on latest Angular’s version, so if you are interested go take a look.

Now, there are two configurations here, one is development and the other is production. As you know, while coding its more handy to have tools like browser sync, watchers, etc. to improve productivity. But dev configuration is usually entirely different from prod, whereas various things change. In Angular, we wish to enable prod mode when turn to production.

Here is a small snippet from the package.json file


"scripts": {
    "start:prod": "npm run tsc && SET NODE_ENV=production && node server.js",
    "start": "if-env NODE_ENV=production && npm run start:prod || npm run start:dev",
    "start:dev": "concurrently \"npm run tsc:w\" \"npm run lite\" ",
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "lite": "lite-server"
  },

I went ahead and omitted some of the code for the sake of brevity.
So, as I mentioned before, there are two different configurations here. The one for production is the start:prod and the one for development is start:dev.

For development server I use the lite-server , by John Papa, which is ideal for a simple development process as this project needed. It provides a NodeJs server with BrowserSync support by default and it has minimal to none configuration.

For production, I created a simple express server, code can be found at server.ts file.

In order to load the server and enable prod mode for Angular, I need to set the NODE_ENV environment variable. This is a NodeJs environment variable which is used to distinguish/flag which configuration the project is currently building.
The start command loads that configuration conditionally by using the if-env npm module. If NODE_ENV value is production, it runs the start:prod command, else it runs the start:dev command.

  • start:dev – It compiles the typescript files, watches them for file changes and also runs the lite web server
  • start:prod – It compiles the typescript files once, sets the NODE_ENV variable to production (this syntax here is only for Windows) and then runs the express server

Quick word on the client code

So, in order to run Angular in prod mode I need to know the value of NODE_ENV variable. But I cannot! This is a NodeJs variable and it is not available on browser! So, what should I do?
Well, my workaround was to create a .json file which I will modify when application is build on production.

So, I created a env-config.json file with one object:


{
    "node_env": ""
}

And in Angular’s main I have the following code:


import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app.module";

let config: { node_env: string } = require("../../env-config.json");
if (config.node_env.trim() === "production") {
    enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule).catch((error) => { console.error(error); });

Pretty much self-explanatory, I fetch the JSON file from the root of the project and I examine the value of config.node_env property. If production, I enable the prod mode.
Quick and simple solution. If you wish to know more on how to properly load JSON files as modules in your code, check out this post.

Quick word on express server

Last part on application code, before I proceed to deployment configuration and Kudu.
I setup an express server, again, very simple, what it does it to get express up and running, specify static content, catch all routes and redirect to index.html (usual stuff for a SPA) and lastly, specify the build configuration.


import * as express from "express";
import * as path from "path";
import http = require("http");
import * as fs from "fs";

const port: number = process.env.PORT || 8080;
const env: string = process.env.NODE_ENV || "development";

let app: express.Application = express();

app.use(express.static(__dirname));
app.use(express.static("src"));
app.use(express.static("node_modules"));
app.all("/*", (request, response, next) => {
    response.sendFile("index.html", { root: __dirname });
});

let contents = fs.readFileSync("env-config.json", "utf8");
let config = JSON.parse(contents);
config.node_env = env;
let json = JSON.stringify(config);
fs.writeFileSync("env-config.json", json, { encoding: "utf8" });

http.createServer(<any>app).listen(port);

Note: This is important! Take a look at the highlighted line. You can’t just have your server listen on port 80. You need to grab the right port from process.env.PORT, else you might experience some weird internal server errors in your Web App, so be careful and always remember to fetch the port from process.env.PORT and set it manually yourself, using the listen method.

Configuring Azure Web App

Setting up an App Service and a Web App on Azure is out of this post context, I assume you know how to do this in first place.
So after you have your Web App set, go to ‘Deployment options’, set to Github provider (provide your credentials if needed) and to the branch that you wish to deploy. Follow similar steps as on the screens below:

Choose ‘Deployment options’

Then choose ‘Github’ as your provider

Finally, setup your credentials, the repository and the branch you wish to deploy from

 

Click OK and done!

You are all set, after a push to your repository, Azure will pick it up and deploy to the Web App.

Hosting application with some Kudu Voodoo

Now comes the fun part.

As you probably know, Azure websites use IISNode to host the node process inside IIS. They need a server.js or an app.js as an entry point of your NodeJs server, so follow these conventions. Also, the name IIS should ring some bells. Oh yeah, you are going to need a web.config for your IIS for your application to work correctly. We’ll go through that in a second.
Lastly, you will wonder and its a fair statement, on how are you going to install all the application dependencies with npm? You can’t run your application without them, right? What about your typescript code? In this case it is not precompiled, so I need to compile on deployment. Jeez, this is getting out of hands, I’ll give up.
Just kidding, you shouldn’t give up, you are just 3-4 steps away from having your application on Azure.

First of all, install the kuduscript tool globally. npm install kuduscript -g

This tool will handle the deployment script creation for you. You will need to make some minor modifications to it, maybe none, depending on your scenarios.
After installing Kuduscript, open a CMD window on your application’s root and type the following command:

kuduscript -y --node --sitePath .

This will create a .deployment and a deploy.cmd files for you on that directory. (For sitePath option, I provided the current directory, which is the root)

The .deployment file is simple:

[config]
command = deploy.cmd

It’s just invoking the deploy.cmd file.
Nice, my suggestion would be to review the deploy.cmd file a bit, get more familiar with the process.
Now, I want not only to install the packages, but I want to compile my Typescript prior deployment. Open the file and navigate to line 100. I modified the code a bit there


:: 3. Install npm packages
IF EXIST "%DEPLOYMENT_TARGET%\package.json" (
  pushd "%DEPLOYMENT_TARGET%"
  call :ExecuteCmd !NPM_CMD! install
  call :ExecuteCmd !NPM_CMD! run tsc
  IF !ERRORLEVEL! NEQ 0 goto error
  popd
)

Take a look at line 4. Initially, it was install --production, but I wanted the devDependencies also, in order to compile.
On line 5, I run the Typescript compiler. This is a custom script that I added there. You are free to add whatever you want, this is mostly the place where you will be toying around, installing dependencies, running tests or compiling, whatever.

You are two steps away. You need to add the web.config file and the .iisnode.yml (yes, they are supporting yaml for IIS, isn’t it great?).
The web.config is pretty standard and you won’t need to bother about it, just paste the following code


<?xml version="1.0" encoding="utf-8"?>  
<configuration>  
    <system.webServer>           
      <handlers>  
           <add name="iisnode" path="server.js" verb="*" modules="iisnode"/>  
     </handlers>  
      <rewrite>  
           <rules>  
                <rule name="LogFile" patternSyntax="ECMAScript" stopProcessing="true">  
                     <match url="iisnode"/>  
                </rule>  
                <rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">                      
                    <match url="^server.js\/debug[\/]?" />  
                </rule>  
                <rule name="StaticContent">  
                     <action type="Rewrite" url="src{{REQUEST_URI}}"/>  
                </rule>  
                <rule name="DynamicContent">  
                     <conditions>  
                          <add input="{{REQUEST_FILENAME}}" matchType="IsFile" negate="True"/>  
                     </conditions>  
                     <action type="Rewrite" url="server.js"/>  
                </rule>  
           </rules>  
      </rewrite>  
   </system.webServer>  
 </configuration>

The only modification needed, was to set the static content folder to be src (this is the folder where my static files reside) as you can see from the highlighted line.

Finally, my favorite part, the YAML file.
I just override the build environment, I didn’t need all the other configuration, so this is the only code line in the file:

node_env: production

Remember, I want to run on production and instruct Angular to enable prod mode, so I needed to set this NodeJs environment variable and the .iisnode.yml file helped me a lot with that.
For full list of configuration overrides, look here.

Finally, Deploy

Everything seems to be ready, go ahead and push.
If you navigate to your Azure portal, to that specific Web App and to deployment options, you will see that it’s currently deploying (if not finished already). After it finishes, open that web site.

Note: If you need to fiddle around with some defects on your web.config or want to check the log files, use the Kudu portal. Just add an .scm after your host name and before azurewebsites, like: http://myhostname.scm.azurewebsites.com
Navigate to Debug Console, investigate the site, the LogFiles, etc.

Summary

In this post I’ve shown how important is Kudu for your NodeJs application! Without it, you couldn’t see your magic hosted on Azure!
What you need to take away is, setting up your deployment is not enough. Azure will not guess what is going on with your application, you need to explicitly instruct it that this is a NodeJs app and here are the various options you wish to set. Use Kudu to make your deployment easier and remember, that even if it is a NodeJs app, you still need a web.config for instructing IIS to host your app.

Happy coding!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s