Fullstack Node.js and Knockout.js app

Sachin the Noder
6 min readNov 20, 2020

Full stack app with Node.js, Express, Postgres and Knockout.js — very easy

This tutorial will show you to create simple web app using

Node.js + Express + PostgreSQL DB to store data on server side. Client side will be a simple and effective Knockout.js

The instructions for installations here are applicable to Ubuntu Linux and other Linux/linux like environment. Please check corrosponding document for your OS.

To get the most out of this tutorial, there are a few prerequisites:

After you install everything start with database as explained below.

PostgreSQL command prompt

Start PostgreSQL using below command

sudo -i -u postgres

Then execute command as below to enter into postgre

psql

you will see command prompt like this

postgres=#

Execute below command in psql command prompt

ALTER USER postgres PASSWORD ‘newPassword’;

Create a database

We can create a database with the SQL command.

postgres=> CREATE DATABASE users_api;

Use the \l or \list command to list the databases as follows

postgres=# \list
List of databases
Name | Owner | Encoding | Collate | Ctype | Access
privileges
-----------+----------+----------+---------+-------+------------
users_api | postgres | UTF8 | en_IN | en_IN |

Connect to the new books_api database as shown below

postgres=> \c users_api
You are now connected to database "users_api" as user "postgres".
users_api=>

Create a table

The last thing we’ll do in the psql command prompt is create a table called users with three fields — two VARCHAR types and an auto-incrementing PRIMARY KEY id.

users_api=>
CREATE TABLE users (
ID SERIAL PRIMARY KEY,
name VARCHAR(30),
email VARCHAR(30)
);

Add two entries to users to have some data to work with.

users_api=> INSERT INTO users (name, email) 
VALUES ('Jerry', 'jerry@example.com'), ('George', 'george@example.com');

Let’s make sure that got added correctly.

users_api=> SELECT * FROM users;
id | name | email
----+--------+--------------------
1 | Jerry | jerry@example.com
2 | George | george@example.com

Setting up an Express server

At this point, we’re finished with all of our PostgreSQL tasks, and we can begin setting up our Node.js app and Express server. Open up a new command prompt and execute following command one after the other.

mkdir noko
cd noko
npm init -y

Directory name can be anything you want — in my example noko stands for Node & Knockout. We’ll want to install Express for the server and node-postgres (pg) to be able to connect to PostgreSQL. path for files path and dotenv to access .env file variables.

npm i express pg path dotenv

The final file structure will be as shown in the image below

Create .env file at he root of the directory with contents as follows — at the root of the directory. This file stores environment variables required for database connection.

DB_USER=postgres
DB_PASSWORD=newPassword
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=books_api

Create a file with name db.js file at the root of the directory. This file reads environment variables from .env file which has database configuration information. This module connects to PostgreSQL database. We check if app is running in production mode (This is required if we are going to host in Heroku) if yes it will take DB connectionstring provided by Heroku otherwise it will use the connectionString built using environment variables. Contents of db.js file is as follows.

require('dotenv').config()const {Pool} = require('pg')
const isProduction = process.env.NODE_ENV === 'production'
const connectionString = `postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_DATABASE}`
console.log(connectionString)
const pool = new Pool({
connectionString: isProduction ? process.env.DATABASE_URL : connectionString,
ssl: {
rejectUnauthorized: false
}
})
module.exports = pool

Create model.js file at the root of directory which will use DB.js module and execute SQL queries against DB.

const pool = require('./db.js')const getUsers = (request, response) => {
pool.query('SELECT * FROM users ORDER BY id DESC', (error, results) => {
if (error) {
throw error
}
response.status(200).json(results.rows)
})
}
const getUserById = (request, response) => {
const id = parseInt(request.params.id)
pool.query('SELECT * FROM users WHERE id = $1', [id], (error, results) => {
if (error) {
throw error
}
response.status(200).json(results.rows)
})
}
const createUser = (request, response) => {
const { name, email } = request.body
pool.query('INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *', [name, email], (error, results) => {
if (error) {
throw error
}
// console.log(results.rows[0])
response.status(201).json(results.rows[0])
})
}
const updateUser = (request, response) => {
const id = parseInt(request.params.id)
const { name, email } = request.body
pool.query(
'UPDATE users SET name = $1, email = $2 WHERE id = $3',
[name, email, id],
(error, results) => {
if (error) {
throw error
}
response.status(200).json(results)
}
)
}
const deleteUser = (request, response) => {
const id = parseInt(request.params.id)
pool.query('DELETE FROM users WHERE id = $1', [id], (error, results) => {
if (error) {
throw error
}
// response.status(200).send(`User deleted with ID: ${id}`)
response.status(200).json(results);
})
}
module.exports = {
getUsers,
getUserById,
createUser,
updateUser,
deleteUser,
}

Create index.js file at the root of your directory with code as shown below

const express = require('express')
const path = require('path')
const PORT = process.env.PORT || 5000
const bodyParser = require('body-parser')
const model = require('./model')
const app = express()
app.use(express.static(path.join(__dirname, 'client')))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended:true,}))
app.get('/', (request, response) => {
response.sendFile( __dirname +"/client/users.html" )
})
app.get('/users', model.getUsers)
app.get('/users/:id', model.getUserById)
app.post('/users', model.createUser)
app.delete('/users/:id', model.deleteUser)
app.put('/users/:id', model.updateUser)
app.listen(PORT, () => console.log(`Listening on ${ PORT }`))

Now create sub directory client at the root of the noko directory app. on the command line you can execute following command.

mkdir client

Now create css & js directory inside client. in css add bootstrap.css and in js add jquery and knockout.js files.

Now add app.js file inside js directory. contents are as follows.

var ViewModel = function () {
var self = this;
self.users = ko.observableArray();
self.error = ko.observable();
self.detail = ko.observable();
self.detailLoading = ko.observable(false);
self.usersLoading = ko.observable(false);
self.newUser = {
name: ko.observable(),
email: ko.observable(),
}
var usersUri = '/users/';function ajaxHelper(uri, method, data) {
self.error(''); // Clear error message
return $.ajax({
type: method,
url: uri,
dataType: 'json',
contentType: 'application/json',
data: data ? JSON.stringify(data) : null
}).fail(function (jqXHR, textStatus, errorThrown) {
self.error(errorThrown + JSON.stringify(jqXHR));
});
}
function getAllUsers() {
self.usersLoading(true)
self.users(undefined)
ajaxHelper(usersUri, 'GET').done(function (data) {
console.log(data)
self.users(data);
self.usersLoading(false)
});
}
self.getUserDetail = function (item) {
self.detailLoading(true)
self.detail(undefined)
ajaxHelper(usersUri + item.id, 'GET').done(function (data) {
self.detail(data[0]);
self.detailLoading(false)
console.log('self.detail '+ JSON.stringify(self.detail()));
});
}
self.deleteUser = function (userParam) {
if (confirm('Are you sure you want to delete ' + userParam.name)) {
ajaxHelper(usersUri + userParam.id, 'DELETE').done(function (data) {
for (var i = self.users().length - 1; i >= 0; --i) {
if (self.users()[i].id == userParam.id) {
self.users.splice(i,1);
self.detail(undefined);
}
}
});
}
}
self.addUser = function (formElement) {
var user = {
name: self.newUser.name(),
email: self.newUser.email()
};
ajaxHelper(usersUri, 'POST', user).done(function (item) {
console.log(item);
self.users.unshift(item);
self.newUser.name('');
self.newUser.email('');
});
}
// Fetch the initial data.
getAllUsers();
};
$(document).ready(function() {
// apply bindings here
ko.applyBindings(new ViewModel());
});

Now add users.html file inside the client directory. The code in this file will be as follows.

<!doctype html>
<html lang="en">
<head>
<title>Users</title>
<style type="text/css">
.scroll {
max-height: 485px;
overflow-y: auto;
}
</style>
<link href="css/bootstrap.min.css" rel="stylesheet" >
<script type='text/javascript' src='js/jquery-3.5.1.min.js'></script>
<script type='text/javascript' src='js/knockout-3.5.1.js'></script>
<script type='text/javascript' src='js/app.js'></script>
</head>
<body>
<div class="container">
<div class="page-header">
<h1>UserService</h1>
</div>
<div class="row">
<div class="col-md-4">
<div class="card ">
<div class="card-header">
<h2 >Add User</h2>
</div>
<div class="card-body scroll">
<form class="form-horizontal" data-bind="submit: addUser">
<div class="form-group" data-bind="with: newUser">
<label for="inputTitle" class="col-sm-10">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputName" data-bind="value:name"/>
</div>
<br>
<label for="inputYear" class="col-sm-10">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail" data-bind="value:email"/>
</div>
<br>
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- 2nd column -->
<div class="col-md-4">
<div class="card " >
<div class="card-header">
<h2>Users</h2>
</div>
<div class="card-body scroll">
<table class="table" data-bind="foreach: users">
<tr>
<td>
<a href="#" data-bind="click: $parent.getUserDetail">
<strong><span data-bind="text: name"></span></strong>
</a>
</td>
<td>
<a href="#" data-bind="click: $parent.deleteUser">
Delete
</a>
</td>
</tr>
</table>
</div>
</div>
<div class="alert alert-danger" data-bind="visible: error">
<p data-bind="text: error"></p>
</div>
</div>
<!-- 3rd column-->
<!-- ko if:detail() -->
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h2 >Detail</h2>
</div>
<table class="table">
<tr><td>id</td><td data-bind="text: detail().id"></td></tr>
<tr><td>name</td><td data-bind="text: detail().name"></td></tr>
<tr><td>email</td><td data-bind="text: detail().email"></td></tr>
</table>
</div>
</div>
<!-- /ko -->
</div>
</div>
</body>
</html>

At this stage the app is ready to run. Run the app from terminal as follows.

node index.js

Now visit http://localhost:5000/

--

--

Sachin the Noder

I am an IT specialist working as a lead Node js programmer