diff --git a/.gitignore b/.gitignore index 00cbbdf..ad7dae2 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,8 @@ typings/ # dotenv environment variables file .env +# mac files +.DS_Store + +# vim swap files +*.swp diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..39e9cbb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: node_js + +node_js: + - "8" + - "6" + - "4" + +services: + - postgresql + +notifications: + email: + on_success: never + on_failure: always diff --git a/README.md b/README.md index 9507acc..8dbe5a6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,106 @@ # fastify-postgres -Fastify PostgreSQL connection plugin + +[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![Build Status](https://travis-ci.org/fastify/fastify-postgres.svg?branch=master)](https://travis-ci.org/fastify/fastify-postgres) + +Fastify PostgreSQL connection plugin, with this you can share the same PostgreSQL connection pool in every part of your server. +Under the hood the [node-postgres](https://github.com/brianc/node-postgres) is used, the options that you pass to `register` will be passed to the PostgreSQL pool builder. + +## Install +``` +npm i fastify-postgres --save +``` +## Usage +Add it to you project with `register` and you are done! +This plugin will add the `pg` namespace in your Fastify instance, with the following properties: +``` +connect: the function to get a connection from the pool +pool: the pool instance +Client: a clinet constructor for a single query +query: an utility to perform a query without a transaction +``` + +Example: +```js +const fastify = require('fastify') + +fastify.register(require('fastify-postgres'), { + connectionString: 'postgres://postgres@localhost/postgres' +}) + +fastify.get('/user/:id', (req, reply) => { + fastify.pg.connect(onConnect) + + function onConnect (err, client, release) { + if (err) return reply.send(err) + + client.query( + 'SELECT id, username, hash, salt FROM users WHERE id=$1', [req.params.id], + function onResult (err, result) { + release() + reply.send(err || result) + } + ) + } +}) + +fastify.listen(3000, err => { + if (err) throw err + console.log(`server listening on ${fastify.server.address().port}`) +}) +``` + +Async await is supported as well! +```js +const fastify = require('fastify') + +fastify.register(require('fastify-postgres'), { + connectionString: 'postgres://postgres@localhost/postgres' +}) + +fastify.get('/user/:id', async (req, reply) => { + const client = await fastify.pg.connect() + const { result } = await client.query( + 'SELECT id, username, hash, salt FROM users WHERE id=$1', [req.params.id], + ) + client.release() + return result +}) + +fastify.listen(3000, err => { + if (err) throw err + console.log(`server listening on ${fastify.server.address().port}`) +}) +``` +Use of `pg.query` +```js +const fastify = require('fastify') + +fastify.register(require('fastify-postgres'), { + connectionString: 'postgres://postgres@localhost/postgres' +}) + +fastify.get('/user/:id', (req, reply) => { + fastify.pg.query( + 'SELECT id, username, hash, salt FROM users WHERE id=$1', [req.params.id], + function onResult (err, result) { + reply.send(err || result) + } + ) +}) + +fastify.listen(3000, err => { + if (err) throw err + console.log(`server listening on ${fastify.server.address().port}`) +}) +``` +As you can see there is no need to close the client, since is done internally. Promises and async await are supported as well. + +## Acknowledgements + +This project is kindly sponsored by: +- [nearForm](http://nearform.com) +- [LetzDoIt](http://www.letzdoitapp.com/) + +## License + +Licensed under [MIT](./LICENSE). diff --git a/index.js b/index.js new file mode 100644 index 0000000..1f858d8 --- /dev/null +++ b/index.js @@ -0,0 +1,50 @@ +'use strict' + +const fp = require('fastify-plugin') +const promisify = require('util.promisify') +const pg = require('pg') + +function fastifyPostgres (fastify, options, next) { + const pool = new pg.Pool(options) + + fastify.decorate('pg', { + connect: pool.connect.bind(pool), + pool: pool, + Client: pg.Client, + query: promisify(query) + }) + + function query (text, value, callback) { + if (typeof value === 'function') { + callback = value + value = null + } + + pool.connect(onConnect) + + function onConnect (err, client, release) { + if (err) return callback(err) + + if (value) { + client.query(text, value, onResult) + } else { + client.query(text, onResult) + } + + function onResult (err, result) { + release() + callback(err, result) + } + } + } + + fastify.addHook('onClose', onClose) + + next() +} + +function onClose (fastify, done) { + fastify.pg.pool.end(done) +} + +module.exports = fp(fastifyPostgres, '>=0.13.1') diff --git a/package.json b/package.json new file mode 100644 index 0000000..4e11d94 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "fastify-postgres", + "version": "0.1.0", + "description": "Fastify PostgreSQL connection plugin", + "main": "index.js", + "scripts": { + "test": "standard && tap test.js", + "postgres": "docker run --rm -p 5432:5432 postgres:9.6-alpine" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/fastify/fastify-postgres.git" + }, + "keywords": [ + "fastify", + "postgres", + "postgresql", + "database", + "connection", + "sql" + ], + "author": "Tomas Della Vedova - @delvedor (http://delved.org)", + "license": "MIT", + "bugs": { + "url": "https://github.com/fastify/fastify-postgres/issues" + }, + "homepage": "https://github.com/fastify/fastify-postgres#readme", + "dependencies": { + "fastify-plugin": "^0.1.1", + "pg": "^7.3.0", + "util.promisify": "^1.0.0" + }, + "devDependencies": { + "fastify": "^0.29.2", + "standard": "^10.0.3", + "tap": "^10.7.2" + } +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..f866a3e --- /dev/null +++ b/test.js @@ -0,0 +1,93 @@ +'use strict' + +const t = require('tap') +const test = t.test +const Fastify = require('fastify') +const fastifyPostgres = require('./index') + +test('fastify.pg namespace should exist', t => { + t.plan(5) + + const fastify = Fastify() + + fastify.register(fastifyPostgres, { + connectionString: 'postgres://postgres@localhost/postgres' + }) + + fastify.ready(err => { + t.error(err) + t.ok(fastify.pg) + t.ok(fastify.pg.connect) + t.ok(fastify.pg.pool) + t.ok(fastify.pg.Client) + fastify.close() + }) +}) + +test('should be able to connect and perform a query', t => { + t.plan(4) + + const fastify = Fastify() + + fastify.register(fastifyPostgres, { + connectionString: 'postgres://postgres@localhost/postgres' + }) + + fastify.ready(err => { + t.error(err) + fastify.pg.connect(onConnect) + }) + + function onConnect (err, client, done) { + t.error(err) + client.query('SELECT NOW()', (err, result) => { + done() + t.error(err) + t.ok(result.rows) + fastify.close() + }) + } +}) + +test('use query util', t => { + t.plan(3) + + const fastify = Fastify() + + fastify.register(fastifyPostgres, { + connectionString: 'postgres://postgres@localhost/postgres' + }) + + fastify.ready(err => { + t.error(err) + fastify.pg.query('SELECT NOW()', (err, result) => { + t.error(err) + t.ok(result.rows) + fastify.close() + }) + }) +}) + +test('use query util with promises', t => { + t.plan(2) + + const fastify = Fastify() + + fastify.register(fastifyPostgres, { + connectionString: 'postgres://postgres@localhost/postgres' + }) + + fastify.ready(err => { + t.error(err) + fastify.pg + .query('SELECT NOW()') + .then(result => { + t.ok(result.rows) + fastify.close() + }) + .catch(err => { + t.fail(err) + fastify.close() + }) + }) +})