Giter Club home page Giter Club logo

koa-skeleton's Introduction

skeleton

koa-skeleton

Dependency Status

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.

Deploy

The Stack

Depends on Node v8.x+:

  • Micro-framework: Koa 2.x. It's very similar to Express except it supports async/await.
  • Database: Postgres.
  • User-input validation: koa-bouncer.
  • HTML templating: React/JSX. HTML is rendered on the server via React JSX templates.
  • Deployment: Heroku. Keeps things easy while you focus on coding your webapp. Forces you to write your webapp statelessly and horizontally-scalably.

Setup

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=''

Configuration (Environment Variables)

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.

Features/Demonstrations

  • User authentication (/register, /login). Sessions are backed by the database and persisted on the client with a cookie session_id=<UUID v4>.
  • Role-based user authorization (i.e. the permission system). Core abstraction is basically 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.
  • Recaptcha integration. Protect any route with Recaptcha by adding the ensureRecaptcha middleware to the route.
  • Flash message cookie. You often want to display simple messages like "Topic created successfully" to the user, but you want the message to last just one request and survive any redirects.
  • Relative human-friendly timestamps like 'Created 4 hours ago' that are updated live via Javascript as the user stays on the page. I accomplish this with the timeago jQuery plugin.
  • Comes with Bootstrap v3.x. I start almost every project with Bootstrap so that I can focus on the back-end code and have a decent looking front-end with minimal effort.
  • 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.
  • Ratelimit middleware. An IP address can only insert a message every X seconds.

Philosophy/Opinions

  • It's better to write explicit glue code between small libraries than credentializing in larger libraries/frameworks that try to do everything for you. When you return to a project in eight months, it's generally easier to catch up by reading explicit glue code then library idiosyncrasies. Similarly, it's easier to catch up by reading SQL strings than your clever ORM backflips.
  • Just write SQL. When you need more complex/composable queries (like a /search endpoint with various filter options), consider using a SQL query building library like knex.js.
  • Use whichever Javascript features that are supported by the lastest stable version of Node. I don't think Babel compilation and the resulting idiosyncrasies are worth the build step.

Conventions

  • 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}`)
    })

Changelog

The following version numbers are meaningless.

  • 4.1.0 20 Apr 2018
    • Replaced bcrypt with scrypt.
    • Made various small schema changes.
  • 4.0.0 26 Nov 2017
    • Replace Pug with React/JSX for HTML templating.
  • 3.1.0 14 Nov 2017
    • Replace Nunjucks with Pug for HTML templating.
  • 3.0.0 25 Apr 2017
    • Removed Babel since the features we want are now supported by Node 7.x.
  • 2.0.0 29 Oct 2015
    • Refactored from Koa 1.x to Koa 2.x.
  • 0.1.0 29 Oct 2016
    • koa-skeleton is a year old, but I just started versioning it started at v0.1.0.
    • Extracted src/db/util.js into pg-extra npm module. Now util.js just exports the connection pool for other modules to use.

License

MIT

koa-skeleton's People

Contributors

danneu avatar jaertgeerts avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

koa-skeleton's Issues

Check db indexes

Made a lot of churn in the schema and need to look at my indexes again. For example, now I'm sorting by created_at now that I use UUID keys yet I haven't created indexes on it.

Document flattening behavior of "nested" routers

  • Only use Router#use on real middleware, don't special case routers.
  • Remove prefixing.
  • Rename Router#mount to Router#merge and point out that nested routers are being flattened.

Need to come up with defined behavior.

Some issues on deployment

The Heroku deployment is not working since you made commit on Removing Babel.

You forgot to remove babel on app.json , the line:
"postdeploy": "NODE_ENV=development node ./babel.reset_db"
should be:
"postdeploy": "NODE_ENV=development node ./sql/reset_db"

It is also important to note that when deploying, reset_db.js requires '../src/config' which will load the default values since it does not initialised the 'dotenv' module. So anything on the .env file will be ignored, so in case the user used a different db name other than postgres://localhost:5432/koa-skeleton then deployment will fail unless they manually change the default values on the config.js.

Also the // Sanity check: Ensure this isn't being run in production on reset_db.js is useless since it will always be on development environment, unless it was run with a specific environment such as NODE_ENV=production node ./sql/reset_db.

I'd recommend requiring dotenv on the reset_db.js so that it will load the environment variables from the .env instead of the defaults.

Parameterize application

Replace singleton modules with function modules that initialize an object that's then threaded through the app.

For example, this would allow tests to invoke a fresh app every time instead of reusing singletons.

Remove babel-register

I was too eager to test my react-template-render library in koa-skeleton and thus replaced my Pug templates with .jsx templates. But now I need Babel to compile .jsx into .js.

The easiest solution was to use babel-register:

require('babel-register')({
presets: ['react'],
extensions: ['.jsx'],
})

But it brings a thick runtime dependency, probably slows down large project boot, and probably degrades the quality of stack traces.

Better would be to compile .jsx to .js physically with a watch script.

It's kind of lame to have a build step at all on a server, but so far .jsx templates have been worth it. Prob on par with what I had with Pug.

Recaptcha middleware is not preventing duplicate requests

I was able to spam-click the submit button in production and create a hundred messages while waiting for the response.

I see the issue. middleware.js#ensureRecaptcha expects recaptcha.ensure to throw, but I refactored recaptcha.ensure to return a bool (or throw for network error).

Add short expiry to flash cookie

The server clears the flash cookie via wrapFlash middleware if the response statusCode < 300 (after rendering the page).

Problem: This means that the user can view a stale flash cookie.

For instance, the server won't get an opportunity to delete the flash cookie if the user doesn't follow the redirect (e.g. internal error) after the cookie is set with an error like 'username taken'. That means that any page the user now visits will show a stale/irrelevant 'username taken' error.

Solution: I can solve this by adding a very short expiry to the flash cookie.

Idea: It could be made even more robust by adding the redirect path to the flash cookie and only rendering the flash message if the flash cookie path matches the current path. This will avoid the case where a user gets a 'username taken' flash error on a page that's not /register or /login.

Improve doc/setup

Hi,

Thanks for the boilerplate.

Two things are missing in the README, it is kind of obvious when you know it but the app returns an error 500 without them :

  1. Add to the .env file (the template do not render without those two variables)
RECAPTCHA_SITEKEY=''
RECAPTCHA_SITESECRET=''
  1. Init the db
npm run reset-db

Fix db.util helpers

Copy updated versions from my other project. Think I fixed a retry bug or something.

Drop babel-register dependency

It's cute to use React on the server for templating, and I like some features like propTypes and its simple component system.

But it's not worth the babel-register runtime dep.

I either need to introduce a jsx->js compile step (might not be so bad if I need a compile step for possible static asset system). Or perhaps I should just port back to Pug.

Live demo link is broken

"There's nothing here, yet." is what the page says when clicking the link to the live demo page.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.