├── public
├── index.js
└── index.html
├── app.arc
├── vercel.json
├── app.json
├── routes
├── index.js
└── quotes.js
├── helper.js
├── .qovery.yml
├── package.json
├── config.js
├── services
├── db.js
└── quotes.js
├── app.js
├── README.md
├── bin
└── www
├── .gitignore
└── db
└── init.sql
/public/index.js:
--------------------------------------------------------------------------------
1 | console.log('Hello world from client-side JS!')
2 |
--------------------------------------------------------------------------------
/app.arc:
--------------------------------------------------------------------------------
1 | @app
2 | begin-app
3 |
4 | @static
5 |
6 | @http
7 |
8 | @tables
9 | data
10 | scopeID *String
11 | dataID **String
12 | ttl TTL
13 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "name": "nodejs-mysql",
4 | "builds": [
5 | { "src": "app.js", "use": "@vercel/node" }
6 | ],
7 | "routes": [
8 | { "src": "/(.*)", "dest": "/app.js" }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Node.js PostgreSQL Demo",
3 | "description": "A basic Node.js API using Express and PostgreSQL",
4 | "repository": "https://github.com/geshan/nodejs-posgresql",
5 | "keywords": ["node", "express", "postgresql"]
6 | }
7 |
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | /* GET home page. */
5 | router.get('/', function(req, res, next) {
6 | res.json({message: 'alive'});
7 | });
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/helper.js:
--------------------------------------------------------------------------------
1 | function getOffset(currentPage = 1, listPerPage) {
2 | return (currentPage - 1) * [listPerPage];
3 | }
4 |
5 | function emptyOrRows(rows) {
6 | if (!rows) {
7 | return [];
8 | }
9 | return rows;
10 | }
11 |
12 | module.exports = {
13 | getOffset,
14 | emptyOrRows
15 | }
16 |
--------------------------------------------------------------------------------
/.qovery.yml:
--------------------------------------------------------------------------------
1 | ---
2 | application:
3 | name: "nodejs-posgresql"
4 | project: "Quotes"
5 | organization: "QoveryCommunity"
6 | databases:
7 | - type: "POSTGRESQL"
8 | name: "quotes"
9 | version: "12"
10 | routers:
11 | - name: "main-nodejs-posgresql"
12 | routes:
13 | - application_name: "nodejs-posgresql"
14 | paths:
15 | - "/*"
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodejs-posgresql",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "node ./bin/www"
7 | },
8 | "dependencies": {
9 | "cookie-parser": "~1.4.4",
10 | "debug": "~2.6.9",
11 | "express": "~4.16.1",
12 | "morgan": "~1.9.1",
13 | "pg": "^8.5.1"
14 | },
15 | "devDependencies": {
16 | "@architect/sandbox": "^3.3.7"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | const env = process.env;
2 |
3 | const config = {
4 | db: { /* do not put password or any sensitive info here, done only for demo */
5 | host: env.DB_HOST || 'otto.db.elephantsql.com',
6 | port: env.DB_PORT || '5432',
7 | user: env.DB_USER || 'cklijfef',
8 | password: env.DB_PASSWORD || 'V1qidES5k3DSJICDRgXtyT8qeu2SPCZp',
9 | database: env.DB_NAME || 'cklijfef',
10 | },
11 | listPerPage: env.LIST_PER_PAGE || 10,
12 | };
13 |
14 | module.exports = config;
15 |
--------------------------------------------------------------------------------
/services/db.js:
--------------------------------------------------------------------------------
1 | const { Pool } = require('pg');
2 | const config = require('../config');
3 | const pool = new Pool(config.db);
4 |
5 | /**
6 | * Query the database using the pool
7 | * @param {*} query
8 | * @param {*} params
9 | *
10 | * @see https://node-postgres.com/features/pooling#single-query
11 | */
12 | async function query(query, params) {
13 | const {rows, fields} = await pool.query(query, params);
14 |
15 | return rows;
16 | }
17 |
18 | module.exports = {
19 | query
20 | }
21 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var path = require('path');
3 | var cookieParser = require('cookie-parser');
4 | var logger = require('morgan');
5 |
6 | var indexRouter = require('./routes/index');
7 | var quotesRouter = require('./routes/quotes');
8 |
9 | var app = express();
10 |
11 | app.use(logger('dev'));
12 | app.use(express.json());
13 | app.use(express.urlencoded({ extended: false }));
14 | app.use(cookieParser());
15 | app.use(express.static(path.join(__dirname, 'public')));
16 |
17 | app.use('/', indexRouter);
18 | app.use('/quotes', quotesRouter);
19 |
20 | module.exports = app;
21 |
--------------------------------------------------------------------------------
/routes/quotes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const quotes = require('../services/quotes');
4 |
5 | /* GET quotes listing. */
6 | router.get('/', async function(req, res, next) {
7 | try {
8 | res.json(await quotes.getMultiple(req.query.page));
9 | } catch (err) {
10 | console.error(`Error while getting quotes `, err.message);
11 | res.status(err.statusCode || 500).json({'message': err.message});
12 | }
13 | });
14 |
15 | /* POST quotes */
16 | router.post('/', async function(req, res, next) {
17 | try {
18 | res.json(await quotes.create(req.body));
19 | } catch (err) {
20 | console.error(`Error while posting quotes `, err.message);
21 | res.status(err.statusCode || 500).json({'message': err.message});
22 | }
23 | });
24 |
25 | module.exports = router;
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nodejs-posgresql
2 |
3 | A demo Quotes REST API using Node.js and PostgreSQL. Built with Express Js for this [Node.js PostgreSQL tutorial](https://geshan.com.np/blog/2021/01/nodejs-postgresql-tutorial/).
4 |
5 | ## Deploy with Heroku
6 |
7 | [](https://heroku.com/deploy?template=https://github.com/geshan/nodejs-posgresql/tree/master)
8 |
9 | ## Deploy with Vercel
10 |
11 | [](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fgeshan%2Fnodejs-posgresql)
12 |
13 | ### Running on Vercel
14 |
15 | At: [https://nodejs-postgresql.vercel.app/quotes](https://nodejs-postgresql.vercel.app/quotes)
16 |
17 | ## Running on Zeet
18 |
19 | At: [https://geshan-nodejs-posgresql.zeet.app/quotes](https://geshan-nodejs-posgresql.zeet.app/quotes)
20 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hi from Begin!
7 |
8 |
9 |
10 |
11 |
12 |

13 |
14 |
15 | Howdy, Beginner!
16 |
17 |
18 | Get started by editing this file at:
19 |
20 |
21 | public/index.html
22 |
23 |
24 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/services/quotes.js:
--------------------------------------------------------------------------------
1 | const db = require('./db');
2 | const helper = require('../helper');
3 | const config = require('../config');
4 |
5 | async function getMultiple(page = 1) {
6 | const offset = helper.getOffset(page, config.listPerPage);
7 | const rows = await db.query(
8 | 'SELECT id, quote, author FROM quote OFFSET $1 LIMIT $2',
9 | [offset, config.listPerPage]
10 | );
11 | const data = helper.emptyOrRows(rows);
12 | const meta = {page};
13 |
14 | return {
15 | data,
16 | meta
17 | }
18 | }
19 |
20 | function validateCreate(quote) {
21 | let messages = [];
22 |
23 | console.log(quote);
24 |
25 | if (!quote) {
26 | messages.push('No object is provided');
27 | }
28 |
29 | if (!quote.quote) {
30 | messages.push('Quote is empty');
31 | }
32 |
33 | if (!quote.author) {
34 | messages.push('Author is empty');
35 | }
36 |
37 | if (quote.quote && quote.quote.length > 255) {
38 | messages.push('Quote cannot be longer than 255 characters');
39 | }
40 |
41 | if (quote.author && quote.author.length > 255) {
42 | messages.push('Author name cannot be longer than 255 characters');
43 | }
44 |
45 | if (messages.length) {
46 | let error = new Error(messages.join());
47 | error.statusCode = 400;
48 |
49 | throw error;
50 | }
51 | }
52 |
53 | async function create(quote){
54 | validateCreate(quote);
55 |
56 | const result = await db.query(
57 | 'INSERT INTO quote(quote, author) VALUES ($1, $2) RETURNING *',
58 | [quote.quote, quote.author]
59 | );
60 | let message = 'Error in creating quote';
61 |
62 | if (result.length) {
63 | message = 'Quote created successfully';
64 | }
65 |
66 | return {message};
67 | }
68 |
69 | module.exports = {
70 | getMultiple,
71 | create
72 | }
73 |
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var app = require('../app');
8 | var debug = require('debug')('nodejs-posgresql: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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/db/init.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE quote (
2 | id SERIAL PRIMARY KEY,
3 | quote character varying(255) NOT NULL UNIQUE,
4 | author character varying(255) NOT NULL,
5 | created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
6 | updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
7 | );
8 |
9 | INSERT INTO quote (id, quote, author) VALUES
10 | (1, 'There are only two kinds of languages: the ones people complain about and the ones nobody uses.', 'Bjarne Stroustrup'),
11 | (2, 'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.', 'Martin Fowler'),
12 | (3, 'First, solve the problem. Then, write the code.', 'John Johnson'),
13 | (4, 'Java is to JavaScript what car is to Carpet.', 'Chris Heilmann'),
14 | (5, 'Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.', 'John Woods'),
15 | (6, 'I''m not a great programmer; I''m just a good programmer with great habits.', 'Kent Beck'),
16 | (7, 'Truth can only be found in one place: the code.', 'Robert C. Martin'),
17 | (8, 'If you have to spend effort looking at a fragment of code and figuring out what it''s doing, then you should extract it into a function and name the function after the "what".', 'Martin Fowler'),
18 | (9, 'The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.', 'Donald Knuth'),
19 | (10, 'SQL, Lisp, and Haskell are the only programming languages that I’ve seen where one spends more time thinking than typing.', 'Philip Greenspun'),
20 | (11, 'Deleted code is debugged code.', 'Jeff Sickel'),
21 | (12, 'There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.', 'C.A.R. Hoare'),
22 | (13, 'Simplicity is prerequisite for reliability.', 'Edsger W. Dijkstra'),
23 | (14, 'There are only two hard things in Computer Science: cache invalidation and naming things.', 'Phil Karlton'),
24 | (15, 'Measuring programming progress by lines of code is like measuring aircraft building progress by weight.', 'Bill Gates'),
25 | (16, 'Controlling complexity is the essence of computer programming.', 'Brian Kernighan'),
26 | (17, 'The only way to learn a new programming language is by writing programs in it.', 'Dennis Ritchie');
27 |
--------------------------------------------------------------------------------