├── 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 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/geshan/nodejs-posgresql/tree/master) 8 | 9 | ## Deploy with Vercel 10 | 11 | [![Deploy with Vercel](https://vercel.com/button)](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 |
25 |

26 | View documentation at: 27 |

28 | https://docs.begin.com 29 |
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 | --------------------------------------------------------------------------------