├── .gitignore ├── _blog ├── todo.png └── node-todo-postges.jpg ├── server ├── models │ └── database.js └── routes │ └── index.js ├── client ├── stylesheets │ └── style.css ├── javascripts │ └── app.js └── views │ └── index.html ├── package.json ├── .jshintrc ├── LICENSE ├── readme.md ├── test ├── load-test.sh └── conn-handling-test-results.txt ├── app.js └── bin └── www /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | .DS_Store 4 | blog.md 5 | node_modules/ 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /_blog/todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjhea0/node-postgres-todo/HEAD/_blog/todo.png -------------------------------------------------------------------------------- /_blog/node-todo-postges.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjhea0/node-postgres-todo/HEAD/_blog/node-todo-postges.jpg -------------------------------------------------------------------------------- /server/models/database.js: -------------------------------------------------------------------------------- 1 | const pg = require('pg'); 2 | const connectionString = process.env.DATABASE_URL || 'postgres://localhost:5432/todo'; 3 | 4 | const client = new pg.Client(connectionString); 5 | client.connect(); 6 | const query = client.query( 7 | 'CREATE TABLE items(id SERIAL PRIMARY KEY, text VARCHAR(40) not null, complete BOOLEAN)'); 8 | query.on('end', () => { client.end(); }); 9 | -------------------------------------------------------------------------------- /client/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | 10 | ul { 11 | list-style-type: none; 12 | padding-left: 10px; 13 | } 14 | 15 | .container { 16 | max-width: 400px; 17 | background-color: #eeeeee; 18 | border: 1px solid black; 19 | } 20 | 21 | .header { 22 | text-align: center; 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-postgres-todo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "supervisor ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.15.1", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.2.0", 12 | "express": "~4.13.4", 13 | "jade": "~1.11.0", 14 | "morgan": "~1.7.0", 15 | "pg": "^6.1.0", 16 | "serve-favicon": "~2.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "globals": { 4 | "esnext": true, 5 | "jasmine": false, 6 | "spyOn": false, 7 | "it": false, 8 | "console": false, 9 | "describe": false, 10 | "expect": false, 11 | "before": false, 12 | "after": false, 13 | "beforeEach": false, 14 | "afterEach": false, 15 | "waits": false, 16 | "waitsFor": false, 17 | "runs": false, 18 | "$": false, 19 | "confirm": false 20 | }, 21 | "esnext": true, 22 | "node" : true, 23 | "browser" : true, 24 | "boss" : false, 25 | "curly": false, 26 | "debug": false, 27 | "devel": false, 28 | "eqeqeq": true, 29 | "evil": true, 30 | "forin": false, 31 | "immed": true, 32 | "laxbreak": false, 33 | "newcap": true, 34 | "noarg": true, 35 | "noempty": false, 36 | "nonew": false, 37 | "nomen": false, 38 | "onevar": true, 39 | "plusplus": false, 40 | "regexp": false, 41 | "undef": true, 42 | "sub": true, 43 | "strict": false, 44 | "white": true, 45 | "unused": false 46 | } 47 | -------------------------------------------------------------------------------- /client/javascripts/app.js: -------------------------------------------------------------------------------- 1 | angular.module('nodeTodo', []) 2 | .controller('mainController', ($scope, $http) => { 3 | $scope.formData = {}; 4 | $scope.todoData = {}; 5 | // Get all todos 6 | $http.get('/api/v1/todos') 7 | .success((data) => { 8 | $scope.todoData = data; 9 | console.log(data); 10 | }) 11 | .error((error) => { 12 | console.log('Error: ' + error); 13 | }); 14 | // Create a new todo 15 | $scope.createTodo = () => { 16 | $http.post('/api/v1/todos', $scope.formData) 17 | .success((data) => { 18 | $scope.formData = {}; 19 | $scope.todoData = data; 20 | console.log(data); 21 | }) 22 | .error((error) => { 23 | console.log('Error: ' + error); 24 | }); 25 | }; 26 | // Delete a todo 27 | $scope.deleteTodo = (todoID) => { 28 | $http.delete('/api/v1/todos/' + todoID) 29 | .success((data) => { 30 | $scope.todoData = data; 31 | console.log(data); 32 | }) 33 | .error((data) => { 34 | console.log('Error: ' + data); 35 | }); 36 | }; 37 | }); 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016 Michael Herman http://www.mherman.org/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # PostgreSQL and NodeJS 2 | 3 | Check out the blog post >> http://mherman.org/blog/2015/02/12/postgresql-and-nodejs 4 | 5 | This is a basic single page application built with Node, Express, Angular, and PostgreSQL. 6 | 7 | ## Quick Start 8 | 9 | 1. Clone the repo 10 | 1. Install dependencies: `npm install` 11 | 1. Start your Postgres server and create a database called "todo" 12 | 1. Create the database tables: `node server/models/database.js` 13 | 1. Start the server: `$ npm start` 14 | 15 | ## Tests 16 | 17 | This comes with a load test using [Apache Bench](http://httpd.apache.org/docs/2.2/programs/ab.html) that by default exercises the API endpoint for the `/api/v1/todos` service: 18 | 19 | ```sh 20 | sh tests/load-test.sh 21 | ``` 22 | 23 | Using this load test it is possible to verify several things: 24 | 25 | - that the database is using as many connections as expected (it polls 26 | PostgreSQL for the number of active connections while it runs) 27 | - the performance of the combined system under different loads 28 | 29 | See the comments in the [script](https://github.com/mjhea0/node-postgres-todo/blob/master/test/load-test.sh) for more information. 30 | 31 | -------------------------------------------------------------------------------- /test/load-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # load-test.sh 4 | # 5 | # Syntax: 6 | # load-test.sh [url] [db] [duration] [users] 7 | # 8 | # By default this will perform a load test using Apache Bench of the 9 | # API URL, with 20 users for 10 seconds. That's enough to get a feel for 10 | # how responsive the system is. Longer tests can detect things like 11 | # memory and resource leaks. More intensive tests can detect things 12 | # like connection limits and operating system limits. 13 | # 14 | # Examples: 15 | # load-test.sh 16 | # load-test.sh http://localhost:3000/api/v1/todos todo 60 100 17 | 18 | # Use bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 19 | set -eou pipefail 20 | IFS=$'\n\t' 21 | 22 | url=${1:-http://localhost:3000/api/v1/todos} 23 | db=${2:-todo} 24 | duration=${3:-10} 25 | users=${4:-20} 26 | 27 | pg_connections() { 28 | db=${1:-$USER} 29 | user=${2:-$USER} 30 | query='SELECT sum(numbackends) FROM pg_stat_database;' 31 | conn=$(psql -U "$user" -t "$db" -c "$query" -w -q | sed -e '/^$/d;s/ //g') 32 | echo "$conn" 33 | } 34 | 35 | ps_running() { 36 | pid=${1:-0} 37 | # shellcheck disable=SC2009 38 | psout=$(ps "$pid" | grep -v ' PID' | awk '{ print $1}') 39 | if [[ -n "$psout" ]]; then return 0; else return 1; fi 40 | } 41 | 42 | ab -c "$users" -t "$duration" "$url" & WAITPID=$! 43 | loop=1 44 | while ps_running "$WAITPID"; do 45 | conn=$(pg_connections todo) 46 | echo "$((loop++))s: $conn connections" 47 | sleep 1 48 | done 49 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const favicon = require('serve-favicon'); 4 | const logger = require('morgan'); 5 | const cookieParser = require('cookie-parser'); 6 | const bodyParser = require('body-parser'); 7 | 8 | const routes = require('./server/routes/index'); 9 | // var users = require('./routes/users'); 10 | 11 | const app = express(); 12 | 13 | // view engine setup 14 | // app.set('views', path.join(__dirname, 'views')); 15 | // app.set('view engine', 'html'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'client'))); 24 | 25 | app.use('/', routes); 26 | // app.use('/users', users); 27 | 28 | // catch 404 and forward to error handler 29 | app.use((req, res, next) => { 30 | var err = new Error('Not Found'); 31 | err.status = 404; 32 | next(err); 33 | }); 34 | 35 | // error handlers 36 | 37 | // development error handler 38 | // will print stacktrace 39 | if (app.get('env') === 'development') { 40 | app.use((err, req, res, next) => { 41 | res.status(err.status || 500); 42 | res.json({ 43 | message: err.message, 44 | error: err 45 | }); 46 | }); 47 | } 48 | 49 | // production error handler 50 | // no stacktraces leaked to user 51 | app.use((err, req, res, next) => { 52 | res.status(err.status || 500); 53 | res.json({ 54 | message: err.message, 55 | error: {} 56 | }); 57 | }); 58 | 59 | 60 | module.exports = app; 61 | -------------------------------------------------------------------------------- /client/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Todo App - with Node + Express + Angular + PostgreSQL 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Todo App

13 |
14 |

Node + Express + Angular + PostgreSQL

15 |
16 |
17 |
18 |
19 | 20 |
21 | 22 |
23 |
24 |
25 |
26 | 29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('node-postgres-todo:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const pg = require('pg'); 4 | const path = require('path'); 5 | const connectionString = process.env.DATABASE_URL || 'postgres://localhost:5432/todo'; 6 | 7 | router.get('/', (req, res, next) => { 8 | res.sendFile(path.join( 9 | __dirname, '..', '..', 'client', 'views', 'index.html')); 10 | }); 11 | 12 | router.get('/api/v1/todos', (req, res, next) => { 13 | const results = []; 14 | // Get a Postgres client from the connection pool 15 | pg.connect(connectionString, (err, client, done) => { 16 | // Handle connection errors 17 | if(err) { 18 | done(); 19 | console.log(err); 20 | return res.status(500).json({success: false, data: err}); 21 | } 22 | // SQL Query > Select Data 23 | const query = client.query('SELECT * FROM items ORDER BY id ASC;'); 24 | // Stream results back one row at a time 25 | query.on('row', (row) => { 26 | results.push(row); 27 | }); 28 | // After all data is returned, close connection and return results 29 | query.on('end', () => { 30 | done(); 31 | return res.json(results); 32 | }); 33 | }); 34 | }); 35 | 36 | router.post('/api/v1/todos', (req, res, next) => { 37 | const results = []; 38 | // Grab data from http request 39 | const data = {text: req.body.text, complete: false}; 40 | // Get a Postgres client from the connection pool 41 | pg.connect(connectionString, (err, client, done) => { 42 | // Handle connection errors 43 | if(err) { 44 | done(); 45 | console.log(err); 46 | return res.status(500).json({success: false, data: err}); 47 | } 48 | // SQL Query > Insert Data 49 | client.query('INSERT INTO items(text, complete) values($1, $2)', 50 | [data.text, data.complete]); 51 | // SQL Query > Select Data 52 | const query = client.query('SELECT * FROM items ORDER BY id ASC'); 53 | // Stream results back one row at a time 54 | query.on('row', (row) => { 55 | results.push(row); 56 | }); 57 | // After all data is returned, close connection and return results 58 | query.on('end', () => { 59 | done(); 60 | return res.json(results); 61 | }); 62 | }); 63 | }); 64 | 65 | router.put('/api/v1/todos/:todo_id', (req, res, next) => { 66 | const results = []; 67 | // Grab data from the URL parameters 68 | const id = req.params.todo_id; 69 | // Grab data from http request 70 | const data = {text: req.body.text, complete: req.body.complete}; 71 | // Get a Postgres client from the connection pool 72 | pg.connect(connectionString, (err, client, done) => { 73 | // Handle connection errors 74 | if(err) { 75 | done(); 76 | console.log(err); 77 | return res.status(500).json({success: false, data: err}); 78 | } 79 | // SQL Query > Update Data 80 | client.query('UPDATE items SET text=($1), complete=($2) WHERE id=($3)', 81 | [data.text, data.complete, id]); 82 | // SQL Query > Select Data 83 | const query = client.query("SELECT * FROM items ORDER BY id ASC"); 84 | // Stream results back one row at a time 85 | query.on('row', (row) => { 86 | results.push(row); 87 | }); 88 | // After all data is returned, close connection and return results 89 | query.on('end', function() { 90 | done(); 91 | return res.json(results); 92 | }); 93 | }); 94 | }); 95 | 96 | router.delete('/api/v1/todos/:todo_id', (req, res, next) => { 97 | const results = []; 98 | // Grab data from the URL parameters 99 | const id = req.params.todo_id; 100 | // Get a Postgres client from the connection pool 101 | pg.connect(connectionString, (err, client, done) => { 102 | // Handle connection errors 103 | if(err) { 104 | done(); 105 | console.log(err); 106 | return res.status(500).json({success: false, data: err}); 107 | } 108 | // SQL Query > Delete Data 109 | client.query('DELETE FROM items WHERE id=($1)', [id]); 110 | // SQL Query > Select Data 111 | var query = client.query('SELECT * FROM items ORDER BY id ASC'); 112 | // Stream results back one row at a time 113 | query.on('row', (row) => { 114 | results.push(row); 115 | }); 116 | // After all data is returned, close connection and return results 117 | query.on('end', () => { 118 | done(); 119 | return res.json(results); 120 | }); 121 | }); 122 | }); 123 | 124 | module.exports = router; 125 | -------------------------------------------------------------------------------- /test/conn-handling-test-results.txt: -------------------------------------------------------------------------------- 1 | $ test/load-test.sh; git stash apply; git diff < /dev/null; test/load-test.sh 2 | This is ApacheBench, Version 2.3 <$Revision: 1663405 $> 3 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 4 | Licensed to The Apache Software Foundation, http://www.apache.org/ 5 | 6 | Benchmarking localhost (be patient) 7 | 1s: 1 connections 8 | 2s: 4 connections 9 | 3s: 4 connections 10 | 4s: 4 connections 11 | 5s: 3 connections 12 | 6s: 4 connections 13 | 7s: 5 connections 14 | 8s: 5 connections 15 | 9s: 6 connections 16 | 10s: 5 connections 17 | Finished 2112 requests 18 | 19 | 20 | Server Software: 21 | Server Hostname: localhost 22 | Server Port: 3000 23 | 24 | Document Path: /api/v1/todos 25 | Document Length: 119 bytes 26 | 27 | Concurrency Level: 20 28 | Time taken for tests: 10.008 seconds 29 | Complete requests: 2112 30 | Failed requests: 0 31 | Total transferred: 650496 bytes 32 | HTML transferred: 251328 bytes 33 | Requests per second: 211.04 [#/sec] (mean) 34 | Time per request: 94.771 [ms] (mean) 35 | Time per request: 4.739 [ms] (mean, across all concurrent requests) 36 | Transfer rate: 63.48 [Kbytes/sec] received 37 | 38 | Connection Times (ms) 39 | min mean[+/-sd] median max 40 | Connect: 0 0 0.2 0 3 41 | Processing: 59 94 16.6 90 183 42 | Waiting: 59 94 16.5 89 183 43 | Total: 61 94 16.6 90 184 44 | 45 | Percentage of the requests served within a certain time (ms) 46 | 50% 90 47 | 66% 96 48 | 75% 101 49 | 80% 104 50 | 90% 118 51 | 95% 130 52 | 98% 139 53 | 99% 145 54 | 100% 184 (longest request) 55 | On branch master 56 | Your branch is ahead of 'origin/master' by 5 commits. 57 | (use "git push" to publish your local commits) 58 | Changes not staged for commit: 59 | (use "git add ..." to update what will be committed) 60 | (use "git checkout -- ..." to discard changes in working directory) 61 | 62 | modified: server/routes/index.js 63 | 64 | no changes added to commit (use "git add" and/or "git commit -a") 65 | diff --git a/server/routes/index.js b/server/routes/index.js 66 | index 45d9a05..e6ca250 100644 67 | --- a/server/routes/index.js 68 | +++ b/server/routes/index.js 69 | @@ -19,6 +19,12 @@ router.post('/api/v1/todos', function(req, res) { 70 | 71 | // Get a Postgres client from the connection pool 72 | pg.connect(connectionString, function(err, client, done) { 73 | + // Handle connection errors 74 | + if(err) { 75 | + done(); 76 | + console.log(err); 77 | + return res.status(500).json({ success: false, data: err}); 78 | + } 79 | 80 | // SQL Query > Insert Data 81 | client.query("INSERT INTO items(text, complete) values($1, $2)", [data.text, data.complete]); 82 | @@ -33,14 +39,10 @@ router.post('/api/v1/todos', function(req, res) { 83 | 84 | // After all data is returned, close connection and return results 85 | query.on('end', function() { 86 | - client.end(); 87 | + done(); 88 | return res.json(results); 89 | }); 90 | 91 | - // Handle Errors 92 | - if(err) { 93 | - console.log(err); 94 | - } 95 | 96 | }); 97 | }); 98 | @@ -51,6 +53,12 @@ router.get('/api/v1/todos', function(req, res) { 99 | 100 | // Get a Postgres client from the connection pool 101 | pg.connect(connectionString, function(err, client, done) { 102 | + // Handle connection errors 103 | + if(err) { 104 | + done(); 105 | + console.log(err); 106 | + return res.status(500).json({ success: false, data: err}); 107 | + } 108 | 109 | // SQL Query > Select Data 110 | var query = client.query("SELECT * FROM items ORDER BY id ASC;"); 111 | @@ -62,15 +70,10 @@ router.get('/api/v1/todos', function(req, res) { 112 | 113 | // After all data is returned, close connection and return results 114 | query.on('end', function() { 115 | - client.end(); 116 | + done(); 117 | return res.json(results); 118 | }); 119 | 120 | - // Handle Errors 121 | - if(err) { 122 | - console.log(err); 123 | - } 124 | - 125 | }); 126 | 127 | }); 128 | @@ -87,6 +90,12 @@ router.put('/api/v1/todos/:todo_id', function(req, res) { 129 | 130 | // Get a Postgres client from the connection pool 131 | pg.connect(connectionString, function(err, client, done) { 132 | + // Handle connection errors 133 | + if(err) { 134 | + done(); 135 | + console.log(err); 136 | + return res.status(500).send(json({ success: false, data: err})); 137 | + } 138 | 139 | // SQL Query > Update Data 140 | client.query("UPDATE items SET text=($1), complete=($2) WHERE id=($3)", [data.text, data.complete, id]); 141 | @@ -101,15 +110,9 @@ router.put('/api/v1/todos/:todo_id', function(req, res) { 142 | 143 | // After all data is returned, close connection and return results 144 | query.on('end', function() { 145 | - client.end(); 146 | + done(); 147 | return res.json(results); 148 | }); 149 | - 150 | - // Handle Errors 151 | - if(err) { 152 | - console.log(err); 153 | - } 154 | - 155 | }); 156 | 157 | }); 158 | @@ -124,6 +127,12 @@ router.delete('/api/v1/todos/:todo_id', function(req, res) { 159 | 160 | // Get a Postgres client from the connection pool 161 | pg.connect(connectionString, function(err, client, done) { 162 | + // Handle connection errors 163 | + if(err) { 164 | + done(); 165 | + console.log(err); 166 | + return res.status(500).json({ success: false, data: err}); 167 | + } 168 | 169 | // SQL Query > Delete Data 170 | client.query("DELETE FROM items WHERE id=($1)", [id]); 171 | @@ -138,15 +147,9 @@ router.delete('/api/v1/todos/:todo_id', function(req, res) { 172 | 173 | // After all data is returned, close connection and return results 174 | query.on('end', function() { 175 | - client.end(); 176 | + done(); 177 | return res.json(results); 178 | }); 179 | - 180 | - // Handle Errors 181 | - if(err) { 182 | - console.log(err); 183 | - } 184 | - 185 | }); 186 | 187 | }); 188 | This is ApacheBench, Version 2.3 <$Revision: 1663405 $> 189 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 190 | Licensed to The Apache Software Foundation, http://www.apache.org/ 191 | 192 | Benchmarking localhost (be patient) 193 | 1s: 1 connections 194 | 2s: 11 connections 195 | 3s: 11 connections 196 | 4s: 11 connections 197 | 5s: 11 connections 198 | 6s: 11 connections 199 | 7s: 11 connections 200 | Completed 5000 requests 201 | 8s: 11 connections 202 | 9s: 11 connections 203 | 10s: 11 connections 204 | Finished 8032 requests 205 | 206 | 207 | Server Software: 208 | Server Hostname: localhost 209 | Server Port: 3000 210 | 211 | Document Path: /api/v1/todos 212 | Document Length: 119 bytes 213 | 214 | Concurrency Level: 20 215 | Time taken for tests: 10.008 seconds 216 | Complete requests: 8032 217 | Failed requests: 0 218 | Total transferred: 2473856 bytes 219 | HTML transferred: 955808 bytes 220 | Requests per second: 802.55 [#/sec] (mean) 221 | Time per request: 24.921 [ms] (mean) 222 | Time per request: 1.246 [ms] (mean, across all concurrent requests) 223 | Transfer rate: 241.39 [Kbytes/sec] received 224 | 225 | Connection Times (ms) 226 | min mean[+/-sd] median max 227 | Connect: 0 1 0.4 0 11 228 | Processing: 13 24 6.6 23 119 229 | Waiting: 13 24 6.6 22 118 230 | Total: 15 25 6.7 23 120 231 | ERROR: The median and mean for the initial connection time are more than twice the standard 232 | deviation apart. These results are NOT reliable. 233 | 234 | Percentage of the requests served within a certain time (ms) 235 | 50% 23 236 | 66% 25 237 | 75% 27 238 | 80% 28 239 | 90% 31 240 | 95% 34 241 | 98% 41 242 | 99% 48 243 | 100% 120 (longest request) 244 | --------------------------------------------------------------------------------