Fullstack Node.js and Knockout.js app
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:
- You should be familiar with JavaScript syntax and fundamentals
- You should have basic knowledge of working with the linux command line
- You should have Node.js and npm installed
- You should have installed Postgres DB — follow instructions here
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.bodypool.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.bodypool.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/