Fast and Frugal App Delivery With an Integrated Production and Development Paradigm
Contents
- Pros and Cons of an integrated Production and Development Setup
- Prerequisites
- Step 1 — Building a Node.js application
- Step 2 — Designing a Production and Development Setup for high-speed Delivery
- Step 3— Setting up Isolated Production and Development Databases
- Conclusion
Introduction
In order to develop and operate a web application, it is necessary to set up and maintain a server to run and develop the web application on. There are many ways to design and manage a development and production environment. Today, we will be looking at a setup that allows us to isolate development and production environments on the same server, effectively reducing our time and resource costs compared to setting up and maintaining two or more separate servers. We will be assessing the pros and cons of such a server setup.
You will build a mock Node application and go on to clone it to create a production and development build. You will then integrate a PostgreSQL database into your applications, using two isolated databases, one for development and another for production. You will be running both the production and development builds at the same time using the PM2 process manager, and developing on the same server that you are serving the production version of the app from for efficiency, frugality, and maximum overlap in environments.
By the end of this tutorial, you will have a high-level understanding of the tradeoffs implicit in configuring and setting up a development and production environment. You will also have a functioning server setup that facilitates both development and production operations in an isolated fashion. The code in this tutorial was tested on March 1st, 2021.
Pros and Cons of an Integrated Production and Development Setup
Before diving into the technical details of setting up an integrated server environment, let’s take a quick look at why one would choose to use a single server for both the development and production operations of an application.
The life-cycle of an application commonly includes a development, testing, and deployment phase, depending on how complex the application is as well as other factors such as how long it is expected to be in use or how many users it is going to support. In order to effectively move from an idea to a fully functioning and fault-tolerant application, a developer needs to take under consideration how to best set up the environment in which they are going to be working in order to minimize the amount of technical and monetary debt that they are going to accrue during the lifetime of an application’s creation and deployment.
Version mismatches in the components of the technical stack being used are a common source of bugs in modern software. Both mismatches in packages and libraries, as well as mismatches resulting from using entirely different database software for development and production, can lead to hard-to-fix bugs. Using a single server for development and production can reduce version mismatch bugs by ensuring that the environments in which you are developing, testing, and deploying your application use similar software packages and operating systems.
The main downside of using a single server for development and production is that it makes it difficult for a team of many developers to work on separate tasks all on a single server, especially if the team is large. All members of the team would have to share access to the server for testing and developing features. They can, of course, chose to build, test, and develop the application locally, or set up a separate set of servers, but this moves away from the paradigm that is explored in this article, which is a single server setup for both development and production. A single server setup is best suited for small teams that need to quickly take an application from development to production with limited resources.
Prerequisites
- You will need a server running Node.js. For this article, we will be using the official NodeJS droplet on Digital Ocean, running Node.js version 12.18.0 and Ubuntu 20.04.
- You will be using the PM2 process manager to set up and manage multiple applications. Refer to PM2: Production-Ready Nodejs Applications in Minutes for assistance with the PM2 process manager.
- You will need to install and setup PostgreSQL and create databases that will be integrated with our Node application. Refer to How To Install and Use PostgreSQL on Ubuntu 18.04 for a detailed guide on how to work with PostgreSQL.
- It’s also necessary to have knowledge of JavaScript in order to understand the code in this tutorial. Refer to How To Code in JavaScript for more information.
Step 1 — Building a Mock Node application
In this step, you will build a Node application that will be later set up for development and production on the same server. You will start by creating a new Node project and creating a GET route. Then you will use PM2 to run and test this application.
By the end of this step, you will have a server with a functioning Node application that is running with PM2.
Begin by creating a new directory and initializing a Node application:
$ mkdir ~/node_application$ cd ~/node_application$ npm init -y$ touch index.js
Next, we are going to create a route in our Node application that lets us access it via a GET request:
const express = require('express');const app = express();const PORT = process.env.PORTapp.get('/',(req,res) => {
res.send("Hello World!\n");
})app.listen(PORT,() => {
console.log(`server is running on port ${PORT}`);
})
Let’s now install express using npm so that our dependencies are met:
npm install express
All that is left to do is create an ecosystem file for the application and run it using the PM2 process manager:
$ pm2 ecosystem$ PORT=8080 pm2 start ecosystem.config.js
And, voila! You have a functioning Node application:
$ curl localhost:8080
Hello World!
Step 2 — Setting up a Production and Development Branch
Now that we have an idea about how the skeleton of our application is going to look like, the next step is to set up two separate directories for our Production and Development environments. The goal is to closely replicate the characteristics of our development environment, including network and proxy setups as well as binary and path configurations, to match those of our production environment in order to reduce the need to emulate them or find workarounds in order to develop new features that are set to be merged into our production build.
Let’s begin by making a copy of our current application to serve as our production application setup:
$ cd ~$ cp -r ~/node_application ./production
Rename the previous application to serve as our development application setup:
$ mv ~/node_application ~/development
Make a change to the development application in order to denote new changes that are to be merged into the production version of our application in the future:
...app.get('/',(req,res) => {
res.send("Hello World, I am the development version!\n");
})...
Let’s also modify the ecosystem.config.js files of both of our application setups so that they have a meaningful name associated with them in the PM2 process manager process list:
module.exports = {
apps : [{
script: 'index.js',
watch: '.', // give your process a meaningful name
name: 'development'
}, {
script: './service-worker/',
watch: ['./service-worker']
}], deploy : {
production : {
user : 'SSH_USERNAME',
host : 'SSH_HOSTMACHINE',
ref : 'origin/master',
repo : 'GIT_REPOSITORY',
path : 'DESTINATION_PATH',
'pre-deploy-local': '',
'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production',
'pre-setup': ''
}
}
};
We are now ready to run two different versions of our application, one for production and another for development:
$ cd ~/development$ PORT=8081 pm2 start ecosystem.config.js$ cd ~/production$ PORT=8080 pm2 start ecosystem.config.js
Now, we have two separate versions of our application, one for development and another for production, each running on a different port:
$ curl localhost:8081
Hello World, I am the development version!$ curl localhost:8080
Hello World!
Step 3— Setting up Isolated Production and Development Databases
There is a good chance that our applications are going to need to be connected to a database at some point in order to manage data in a scalable fashion. We will want to have isolated production and development databases so that if we want to test how our application manages different data models, we can start populating our database with test data, and not have that mix in with our production data contained in the database.
We will do this by setting up a database server and connecting to it on a single port for both our development and production application setups, however, we will use a separate database for each of our application setups. This method of isolating databases is particularly useful when quickly prototyping an app and bringing it to production because it circumvents the need to maintain two separate database servers. We are effectively saving time and resources by sticking to a single database server and isolating our databases to suit our development and production purposes.
In this step, we are going to be installing PostgreSQL on our server, and creating two separate databases for our production and development application setups, which we will then be integrating into our Node applications and interfacing with using a collection of Node.js modules. We will be assuming that a PostgreSQL server is installed and initialized on the server we are working with. Please refer to How To Install and Use PostgreSQL on Ubuntu 18.04 for help with installing and initializing the PostgreSQL database server.
Let’s first begin with creating two separate databases for each of our two application setups:
$ sudo -u postgres createdb production $ sudo -u postgres createdb development
Now, we are ready to connect our databases to our applications using the node-postgres package from npm:
$ cd ~/development$ npm i pg
Let’s create a users table to add names to with our app:
$ sudo -u postgres psql development
development=# CREATE TABLE users (
development(# name CHAR(50)
development(# );
In the above code block, we spawned the PostgreSQL database management shell with the permissions of the postgres user and added a new “users” model that contains a string “name” field.
Now, we are ready to connect to the database server and test adding data to our database:
const express = require('express');// Import package for interfacing with PostgreSQL
const { Client } = require('pg');// Configure client
const client = new Client({
user: 'postgres',
host: 'localhost',
database: 'development',
password: 'password',
port: '5432'
})// Connect to server
client.connect(err => {
if (err) console.log(err.stack)
})const app = express();
const PORT = process.env.PORTapp.get('/',(req,res) => {
res.send("Hello World, I am the development version!\n");
})// Create a route to test adding data to database
app.get('/test-pg/users/:name', (req, res) => {
let name = req.params.name
client.query('INSERT INTO users(name) VALUES($1) RETURNING *', [name], (err, result) => {
if (err) {
res.status(500).send(err.stack)
} else {
res.status(200).send(result.rows[0])
}
})
})app.listen(PORT,() => {
console.log(`server is running on port ${PORT}`);
})
In order to test our database connectivity, we are going to use the /test-pg/users route that we created in the code block above and pass in a name to see if our database successfully adds the data to the database and echos back the newly created entry:
$ curl localhost:8081/test-pg/users/Bob
{"name":"Bob"}
Setting up the production database is very similar, the only difference is that we are going to specify in the client configuration of our production application that we are connecting to the database with the name “production” that we created earlier in this step. Since we are using the same database server, our password, username, host, and port remain unchanged. Don’t forget to create a “users” relation in the production database similar to how we created one for our development database.
Conclusion
In conclusion, the application development process can be made more speedy and efficient by using an integrated development and production server paradigm. An integrated setup comes with its own pros and cons to consider, but for small teams that are looking to move quickly and frugally, an integrated setup may be worth considering.
See you in the next article.