🗺 a simple app that demonstrates koa + postgres + other useful abstractions
An example Koa application that glues together Koa + Postgres + good defaults + common abstractions that I frequently use to create web applications.
Also used as a test bed for my own libraries.
Originally this project was intended to be forked and modified, but it's grown to the point that it's better left as a demonstration of how one can structure a Koa + Postgres application.
Depends on Node v8.x+:
You must have Postgres installed. I recommend http://postgresapp.com/ for OSX.
createdb koa-skeleton
git clone [email protected]:danneu/koa-skeleton.git
cd koa-skeleton
touch .env
npm install
npm run reset-db
npm run start-dev
> Server is listening on http://localhost:3000...
Create a .env
file in the root directory which will let you set environment variables. npm run start-dev
will read from it.
Example .env
:
DATABASE_URL=postgres://username:password@localhost:5432/my-database
DEBUG=app:*
RECAPTCHA_SITEKEY=''
RECAPTCHA_SITESECRET=''
koa-skeleton is configured with environment variables.
You can set these by putting them in a .env
file at the project root (good
for development) or by exporting them in the environment (good for production,
like on Heroku).
You can look at src/config.js
to view these and their defaults.
Evironment Variable | Type | Default | Description |
---|---|---|---|
NODE_ENV | String | "development" | Set to "production" on the production server to enable some optimizations and security checks that are turned off in development for convenience. |
PORT | Integer | 3000 | Overriden by Heroku in production. |
DATABASE_URL | String | "postgres://localhost:5432/koa-skeleton" | Overriden by Heroku in production if you use its Heroku Postgres addon. |
TRUST_PROXY | Boolean | false | Set it to the string "true" to turn it on. Turn it on if you're behind a proxy like Cloudflare which means you can trust the IP address supplied in the X-Forwarded-For header. If so, then ctx.request.ip will use that header if it's set. |
HOSTNAME | String | undefined | Set it to your hostname in production to enable basic CSRF protection. i.e. example.com , subdomain.example.com . If set, then any requests not one of `GET |
RECAPTCHA_SITEKEY | String | undefined | Must be set to enable the Recaptcha system. https://www.google.com/recaptcha |
RECAPTCHA_SITESECRET | String | undefined | Must be set to enable the Recaptcha system. https://www.google.com/recaptcha |
MESSAGES_PER_PAGE | Integer | 10 | Determines how many messages to show per page when viewing paginated lists |
USERS_PER_PAGE | Integer | 10 | Determines how many users to show per page when viewing paginated lists |
Don't access process.env.*
directly in the app.
Instead, require the src/config.js
and access them there.
session_id=<UUID v4>
.can(currUser, action, target) -> Boolean
, which is implemented as one big switch statement. For example: can(currUser, 'READ_TOPIC', topic)
. Inspired by ryanb/cancan, though my abstraction is an ultra simplification. My user table often has a role
column that's either one of ADMIN
| MOD
| MEMBER
(default) | BANNED
.ensureRecaptcha
middleware to the route.npm run reset-db
. During early development, I like to have a reset-db
command that I can spam that will delete the schema, recreate it, and insert any sample data I put in a seeds.sql
file.Aside from validation, never access query/body/url params via the Koa default like ctx.request.body.username
. Instead, use koa-bouncer to move these to the ctx.vals
object and access them there. This forces you to self-document what params you expect at the top of your route and prevents the case where you forget to validate params.
router.post('/users', async (ctx, next) => {
// Validation
ctx
.validateBody('uname')
.isString('Username required')
.trim()
.isLength(3, 15, 'Username must be 3-15 chars')
ctx
.validateBody('email')
.optional()
.isString()
.trim()
.isEmail()
ctx
.validateBody('password1')
.isString('Password required')
.isLength(6, 100, 'Password must be 6-100 chars')
ctx
.validateBody('password2')
.isString('Password confirmation required')
.eq(ctx.vals.password1, 'Passwords must match')
// Validation passed. Access the above params via `ctx.vals` for
// the remainder of the route to ensure you're getting the validated
// version.
const user = await db.insertUser(
ctx.vals.uname,
ctx.vals.password1,
ctx.vals.email
)
ctx.redirect(`/users/${user.uname}`)
})
The following version numbers are meaningless.
4.1.0
20 Apr 2018
4.0.0
26 Nov 2017
3.1.0
14 Nov 2017
3.0.0
25 Apr 2017
2.0.0
29 Oct 2015
0.1.0
29 Oct 2016
src/db/util.js
into pg-extra
npm module.MIT