├── .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 |
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 |
--------------------------------------------------------------------------------