Repository: adrianObel/koa2-api-boilerplate
Branch: master
Commit: 7c9c26b7f9d6
Files: 27
Total size: 27.8 KB
Directory structure:
gitextract_sjp2bf63/
├── .babelrc
├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── bin/
│ └── server.js
├── config/
│ ├── env/
│ │ ├── common.js
│ │ ├── development.js
│ │ ├── production.js
│ │ └── test.js
│ ├── index.js
│ └── passport.js
├── index.js
├── package.json
├── src/
│ ├── middleware/
│ │ ├── index.js
│ │ └── validators.js
│ ├── models/
│ │ └── users.js
│ ├── modules/
│ │ ├── auth/
│ │ │ ├── controller.js
│ │ │ └── router.js
│ │ ├── index.js
│ │ └── users/
│ │ ├── controller.js
│ │ └── router.js
│ └── utils/
│ └── auth.js
└── test/
├── auth.spec.js
├── users.spec.js
└── utils.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
"es2015-node5",
"stage-0"
]
}
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
# A special property that should be specified at the top of the file outside of
# any sections. Set to true to stop .editor config file search on current file
root = true
[*]
# Indentation style
# Possible values - tab, space
indent_style = space
# Indentation size in single-spaced characters
# Possible values - an integer, tab
indent_size = 2
# Line ending file format
# Possible values - lf, crlf, cr
end_of_line = lf
# File character encoding
# Possible values - latin1, utf-8, utf-16be, utf-16le
charset = utf-8
# Denotes whether to trim whitespace at the end of lines
# Possible values - true, false
trim_trailing_whitespace = true
# Denotes whether file should end with a newline
# Possible values - true, false
insert_final_newline = true
================================================
FILE: .eslintrc.json
================================================
{
"parser": "babel-eslint",
"extends": "standard",
"env": {
"node": true,
"mocha": true
}
}
================================================
FILE: .gitignore
================================================
# Created by https://www.gitignore.io/api/node,sublimetext
### Node ###
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
node_modules
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
### SublimeText ###
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
*.sublime-project
# sftp configuration file
sftp-config.json
#Documentation
docs
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Adrian Obelmejias <adrian@obel.me>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
#koa2-api-boilerplate
[](http://standardjs.com)
Boilerplate for building APIs with [koa2](https://github.com/koajs/koa/tree/v2.x) and mongodb.
This project covers basic necessities of most APIs.
* Authentication (passport & jwt)
* Database (mongoose)
* Testing (mocha)
* Doc generation with apidoc
* linting using standard
##Requirements
* node __^4.0.0__
* npm __^3.0.0__
##Installation
```bash
git clone https://github.com/adrianObel/koa2-api-boilerplate.git
```
##Features
* [koa2](https://github.com/koajs/koa/tree/v2.x)
* [koa-router](https://github.com/alexmingoia/koa-router)
* [koa-bodyparser](https://github.com/koajs/bodyparser)
* [koa-generic-session](https://github.com/koajs/generic-session)
* [koa-logger](https://github.com/koajs/logger)
* [MongoDB](http://mongodb.org/)
* [Mongoose](http://mongoosejs.com/)
* [Passport](http://passportjs.org/)
* [Nodemon](http://nodemon.io/)
* [Mocha](https://mochajs.org/)
* [apidoc](http://apidocjs.com/)
* [Babel](https://github.com/babel/babel)
* [ESLint](http://eslint.org/)
##Structure
```
├── bin
│ └── server.js # Bootstrapping and entry point
├── config # Server configuration settings
│ ├── env # Environment specific config
│ │ ├── common.js
│ │ ├── development.js
│ │ ├── production.js
│ │ └── test.js
│ ├── index.js # Config entrypoint - exports config according to envionrment and commons
│ └── passport.js # Passportjs config of strategies
├── src # Source code
│ ├── modules
│ │ ├── controller.js # Module-specific controllers
│ │ └── router.js # Router definitions for module
│ ├── models # Mongoose models
│ └── middleware # Custom middleware
│ └── validators # Validation middleware
└── test # Unit tests
```
##Usage
* `npm start` Start server on live mode
* `npm run dev` Start server on dev mode with nodemon
* `npm run docs` Generate API documentation
* `npm test` Run mocha tests
##Documentation
API documentation is written inline and generated by [apidoc](http://apidocjs.com/).
Visit `http://localhost:5000/docs/` to view docs
##License
MIT
================================================
FILE: bin/server.js
================================================
import Koa from 'koa'
import bodyParser from 'koa-bodyparser'
import convert from 'koa-convert'
import logger from 'koa-logger'
import mongoose from 'mongoose'
import session from 'koa-generic-session'
import passport from 'koa-passport'
import mount from 'koa-mount'
import serve from 'koa-static'
import config from '../config'
import { errorMiddleware } from '../src/middleware'
const app = new Koa()
app.keys = [config.session]
mongoose.Promise = global.Promise
mongoose.connect(config.database)
app.use(convert(logger()))
app.use(bodyParser())
app.use(session())
app.use(errorMiddleware())
app.use(convert(mount('/docs', serve(`${process.cwd()}/docs`))))
require('../config/passport')
app.use(passport.initialize())
app.use(passport.session())
const modules = require('../src/modules')
modules(app)
app.listen(config.port, () => {
console.log(`Server started on ${config.port}`)
})
export default app
================================================
FILE: config/env/common.js
================================================
export default {
port: process.env.PORT || 5000
}
================================================
FILE: config/env/development.js
================================================
export default {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://localhost:27017/koa2-boilerplate-dev'
}
================================================
FILE: config/env/production.js
================================================
export default {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://localhost:27017/koa2-boilerplate-prod'
}
================================================
FILE: config/env/test.js
================================================
export default {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://localhost:27017/koa2-boilerplate-test'
}
================================================
FILE: config/index.js
================================================
import common from './env/common'
const env = process.env.NODE_ENV || 'development'
const config = require(`./env/${env}`).default
export default Object.assign({}, common, config)
================================================
FILE: config/passport.js
================================================
import passport from 'koa-passport'
import User from '../src/models/users'
import { Strategy } from 'passport-local'
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id, '-password')
done(null, user)
} catch (err) {
done(err)
}
})
passport.use('local', new Strategy({
usernameField: 'username',
passwordField: 'password'
}, async (username, password, done) => {
try {
const user = await User.findOne({ username })
if (!user) { return done(null, false) }
try {
const isMatch = await user.validatePassword(password)
if (!isMatch) { return done(null, false) }
done(null, user)
} catch (err) {
done(err)
}
} catch (err) {
return done(err)
}
}))
================================================
FILE: index.js
================================================
require('babel-core/register')()
require('babel-polyfill')
require('./bin/server.js')
================================================
FILE: package.json
================================================
{
"name": "koa2-api-boilerplate",
"version": "2.2.0",
"description": "Koa2 boilerplate covering essentials for APIs",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "./node_modules/.bin/nodemon index.js",
"test": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-register --require babel-polyfill",
"lint": "eslint src/**/*.js",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs"
},
"keywords": [
"koa2-api-boilerplate",
"api",
"koa",
"koa2",
"boilerplate",
"es6",
"mongoose",
"passportjs",
"apidoc"
],
"author": "Adrian Obelmejias <adrian@obel.me>",
"license": "MIT",
"apidoc": {
"title": "koa2-api-boilerplate",
"url": "localhost:5000"
},
"repository": {
"type": "git",
"url": "https://github.com/adrianObel/koa2-api-boilerplate"
},
"dependencies": {
"apidoc": "^0.16.1",
"babel-core": "^6.5.1",
"babel-polyfill": "^6.5.0",
"babel-preset-es2015-node5": "^1.2.0",
"babel-preset-stage-0": "^6.5.0",
"bcrypt": "^0.8.5",
"glob": "^7.0.0",
"jsonwebtoken": "^7.1.9",
"koa": "^2.0.0-alpha.6",
"koa-bodyparser": "^3.0.0",
"koa-convert": "^1.2.0",
"koa-generic-session": "^1.10.1",
"koa-logger": "^2.0.0",
"koa-mount": "^1.3.0",
"koa-passport": "^2.0.1",
"koa-router": "^7.0.1",
"koa-static": "^2.0.0",
"mongoose": "^4.4.3",
"passport-local": "^1.0.0"
},
"devDependencies": {
"babel-eslint": "^6.0.2",
"babel-register": "^6.5.1",
"chai": "^3.5.0",
"eslint": "^3.4.0",
"eslint-config-standard": "^6.0.0",
"eslint-plugin-promise": "^2.0.1",
"eslint-plugin-standard": "^2.0.0",
"mocha": "^3.0.2",
"nodemon": "^1.8.1",
"supertest": "^2.0.0"
}
}
================================================
FILE: src/middleware/index.js
================================================
export function errorMiddleware () {
return async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.status = err.status || 500
ctx.body = err.message
ctx.app.emit('error', err, ctx)
}
}
}
================================================
FILE: src/middleware/validators.js
================================================
import User from '../models/users'
import config from '../../config'
import { getToken } from '../utils/auth'
import { verify } from 'jsonwebtoken'
export async function ensureUser (ctx, next) {
const token = getToken(ctx)
if (!token) {
ctx.throw(401)
}
let decoded = null
try {
decoded = verify(token, config.token)
} catch (err) {
ctx.throw(401)
}
ctx.state.user = await User.findById(decoded.id, '-password')
if (!ctx.state.user) {
ctx.throw(401)
}
return next()
}
================================================
FILE: src/models/users.js
================================================
import mongoose from 'mongoose'
import bcrypt from 'bcrypt'
import config from '../../config'
import jwt from 'jsonwebtoken'
const User = new mongoose.Schema({
type: { type: String, default: 'User' },
name: { type: String },
username: { type: String, required: true, unique: true },
password: { type: String, required: true }
})
User.pre('save', function preSave (next) {
const user = this
if (!user.isModified('password')) {
return next()
}
new Promise((resolve, reject) => {
bcrypt.genSalt(10, (err, salt) => {
if (err) { return reject(err) }
resolve(salt)
})
})
.then(salt => {
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) { throw new Error(err) }
user.password = hash
next(null)
})
})
.catch(err => next(err))
})
User.methods.validatePassword = function validatePassword (password) {
const user = this
return new Promise((resolve, reject) => {
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) { return reject(err) }
resolve(isMatch)
})
})
}
User.methods.generateToken = function generateToken () {
const user = this
return jwt.sign({ id: user.id }, config.token)
}
export default mongoose.model('user', User)
================================================
FILE: src/modules/auth/controller.js
================================================
import passport from 'koa-passport'
/**
* @apiDefine TokenError
* @apiError Unauthorized Invalid JWT token
*
* @apiErrorExample {json} Unauthorized-Error:
* HTTP/1.1 401 Unauthorized
* {
* "status": 401,
* "error": "Unauthorized"
* }
*/
/**
* @api {post} /auth Authenticate user
* @apiVersion 1.0.0
* @apiName AuthUser
* @apiGroup Auth
*
* @apiParam {String} username User username.
* @apiParam {String} password User password.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "username": "johndoe@gmail.com", "password": "foo" }' localhost:5000/auth
*
* @apiSuccess {Object} user User object
* @apiSuccess {ObjectId} user._id User id
* @apiSuccess {String} user.name User name
* @apiSuccess {String} user.username User username
* @apiSuccess {String} token Encoded JWT
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "user": {
* "_id": "56bd1da600a526986cf65c80"
* "username": "johndoe"
* },
* "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
* }
*
* @apiError Unauthorized Incorrect credentials
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 401 Unauthorized
* {
* "status": 401,
* "error": "Unauthorized"
* }
*/
export async function authUser (ctx, next) {
return passport.authenticate('local', (user) => {
if (!user) {
ctx.throw(401)
}
const token = user.generateToken()
const response = user.toJSON()
delete response.password
ctx.body = {
token,
user: response
}
})(ctx, next)
}
================================================
FILE: src/modules/auth/router.js
================================================
import * as auth from './controller'
export const baseUrl = '/auth'
export default [
{
method: 'POST',
route: '/',
handlers: [
auth.authUser
]
}
]
================================================
FILE: src/modules/index.js
================================================
import glob from 'glob'
import Router from 'koa-router'
exports = module.exports = function initModules (app) {
glob(`${__dirname}/*`, { ignore: '**/index.js' }, (err, matches) => {
if (err) { throw err }
matches.forEach((mod) => {
const router = require(`${mod}/router`)
const routes = router.default
const baseUrl = router.baseUrl
const instance = new Router({ prefix: baseUrl })
routes.forEach((config) => {
const {
method = '',
route = '',
handlers = []
} = config
const lastHandler = handlers.pop()
instance[method.toLowerCase()](route, ...handlers, async function(ctx) {
return await lastHandler(ctx)
})
app
.use(instance.routes())
.use(instance.allowedMethods())
})
})
})
}
================================================
FILE: src/modules/users/controller.js
================================================
import User from '../../models/users'
/**
* @api {post} /users Create a new user
* @apiPermission
* @apiVersion 1.0.0
* @apiName CreateUser
* @apiGroup Users
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "username": "johndoe", "password": "secretpasas" } }' localhost:5000/users
*
* @apiParam {Object} user User object (required)
* @apiParam {String} user.username Username.
* @apiParam {String} user.password Password.
*
* @apiSuccess {Object} users User object
* @apiSuccess {ObjectId} users._id User id
* @apiSuccess {String} users.name User name
* @apiSuccess {String} users.username User username
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "user": {
* "_id": "56bd1da600a526986cf65c80"
* "name": "John Doe"
* "username": "johndoe"
* }
* }
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*/
export async function createUser (ctx) {
const user = new User(ctx.request.body.user)
try {
await user.save()
} catch (err) {
ctx.throw(422, err.message)
}
const token = user.generateToken()
const response = user.toJSON()
delete response.password
ctx.body = {
user: response,
token
}
}
/**
* @api {get} /users Get all users
* @apiPermission user
* @apiVersion 1.0.0
* @apiName GetUsers
* @apiGroup Users
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5000/users
*
* @apiSuccess {Object[]} users Array of user objects
* @apiSuccess {ObjectId} users._id User id
* @apiSuccess {String} users.name User name
* @apiSuccess {String} users.username User username
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "users": [{
* "_id": "56bd1da600a526986cf65c80"
* "name": "John Doe"
* "username": "johndoe"
* }]
* }
*
* @apiUse TokenError
*/
export async function getUsers (ctx) {
const users = await User.find({}, '-password')
ctx.body = { users }
}
/**
* @api {get} /users/:id Get user by id
* @apiPermission user
* @apiVersion 1.0.0
* @apiName GetUser
* @apiGroup Users
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5000/users/56bd1da600a526986cf65c80
*
* @apiSuccess {Object} users User object
* @apiSuccess {ObjectId} users._id User id
* @apiSuccess {String} users.name User name
* @apiSuccess {String} users.username User username
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "user": {
* "_id": "56bd1da600a526986cf65c80"
* "name": "John Doe"
* "username": "johndoe"
* }
* }
*
* @apiUse TokenError
*/
export async function getUser (ctx, next) {
try {
const user = await User.findById(ctx.params.id, '-password')
if (!user) {
ctx.throw(404)
}
ctx.body = {
user
}
} catch (err) {
if (err === 404 || err.name === 'CastError') {
ctx.throw(404)
}
ctx.throw(500)
}
if (next) { return next() }
}
/**
* @api {put} /users/:id Update a user
* @apiPermission
* @apiVersion 1.0.0
* @apiName UpdateUser
* @apiGroup Users
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X PUT -d '{ "user": { "name": "Cool new Name" } }' localhost:5000/users/56bd1da600a526986cf65c80
*
* @apiParam {Object} user User object (required)
* @apiParam {String} user.name Name.
* @apiParam {String} user.username Username.
*
* @apiSuccess {Object} users User object
* @apiSuccess {ObjectId} users._id User id
* @apiSuccess {String} users.name Updated name
* @apiSuccess {String} users.username Updated username
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "user": {
* "_id": "56bd1da600a526986cf65c80"
* "name": "Cool new name"
* "username": "johndoe"
* }
* }
*
* @apiError UnprocessableEntity Missing required parameters
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 422 Unprocessable Entity
* {
* "status": 422,
* "error": "Unprocessable Entity"
* }
*
* @apiUse TokenError
*/
export async function updateUser (ctx) {
const user = ctx.body.user
Object.assign(user, ctx.request.body.user)
await user.save()
ctx.body = {
user
}
}
/**
* @api {delete} /users/:id Delete a user
* @apiPermission
* @apiVersion 1.0.0
* @apiName DeleteUser
* @apiGroup Users
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X DELETE localhost:5000/users/56bd1da600a526986cf65c80
*
* @apiSuccess {StatusCode} 200
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "success": true
* }
*
* @apiUse TokenError
*/
export async function deleteUser (ctx) {
const user = ctx.body.user
await user.remove()
ctx.status = 200
ctx.body = {
success: true
}
}
================================================
FILE: src/modules/users/router.js
================================================
import { ensureUser } from '../../middleware/validators'
import * as user from './controller'
export const baseUrl = '/users'
export default [
{
method: 'POST',
route: '/',
handlers: [
user.createUser
]
},
{
method: 'GET',
route: '/',
handlers: [
ensureUser,
user.getUsers
]
},
{
method: 'GET',
route: '/:id',
handlers: [
ensureUser,
user.getUser
]
},
{
method: 'PUT',
route: '/:id',
handlers: [
ensureUser,
user.getUser,
user.updateUser
]
},
{
method: 'DELETE',
route: '/:id',
handlers: [
ensureUser,
user.getUser,
user.deleteUser
]
}
]
================================================
FILE: src/utils/auth.js
================================================
export function getToken (ctx) {
const header = ctx.request.header.authorization
if (!header) {
return null
}
const parts = header.split(' ')
if (parts.length !== 2) {
return null
}
const scheme = parts[0]
const token = parts[1]
if (/^Bearer$/i.test(scheme)) {
return token
}
return null
}
================================================
FILE: test/auth.spec.js
================================================
import app from '../bin/server'
import supertest from 'supertest'
import { expect, should } from 'chai'
import { cleanDb, authUser } from './utils'
should()
const request = supertest.agent(app.listen())
const context = {}
describe('Auth', () => {
before((done) => {
cleanDb()
authUser(request, (err, { user, token }) => {
if (err) { return done(err) }
context.user = user
context.token = token
done()
})
})
describe('POST /auth', () => {
it('should throw 401 if credentials are incorrect', (done) => {
request
.post('/auth')
.set('Accept', 'application/json')
.send({ username: 'supercoolname', password: 'wrongpassword' })
.expect(401, done)
})
it('should auth user', (done) => {
request
.post('/auth')
.set('Accept', 'application/json')
.send({ username: 'test', password: 'pass' })
.expect(200, (err, res) => {
if (err) { return done(err) }
res.body.user.should.have.property('username')
res.body.user.username.should.equal('test')
expect(res.body.user.password).to.not.exist
context.user = res.body.user
context.token = res.body.token
done()
})
})
})
})
================================================
FILE: test/users.spec.js
================================================
import app from '../bin/server'
import supertest from 'supertest'
import { expect, should } from 'chai'
import { cleanDb } from './utils'
should()
const request = supertest.agent(app.listen())
const context = {}
describe('Users', () => {
before((done) => {
cleanDb()
done()
})
describe('POST /users', () => {
it('should reject signup when data is incomplete', (done) => {
request
.post('/users')
.set('Accept', 'application/json')
.send({ username: 'supercoolname' })
.expect(422, done)
})
it('should sign up', (done) => {
request
.post('/users')
.set('Accept', 'application/json')
.send({ user: { username: 'supercoolname', password: 'supersecretpassword' } })
.expect(200, (err, res) => {
if (err) { return done(err) }
res.body.user.should.have.property('username')
res.body.user.username.should.equal('supercoolname')
expect(res.body.user.password).to.not.exist
context.user = res.body.user
context.token = res.body.token
done()
})
})
})
describe('GET /users', () => {
it('should not fetch users if the authorization header is missing', (done) => {
request
.get('/users')
.set('Accept', 'application/json')
.expect(401, done)
})
it('should not fetch users if the authorization header is missing the scheme', (done) => {
request
.get('/users')
.set({
Accept: 'application/json',
Authorization: '1'
})
.expect(401, done)
})
it('should not fetch users if the authorization header has invalid scheme', (done) => {
const { token } = context
request
.get('/users')
.set({
Accept: 'application/json',
Authorization: `Unknown ${token}`
})
.expect(401, done)
})
it('should not fetch users if token is invalid', (done) => {
request
.get('/users')
.set({
Accept: 'application/json',
Authorization: 'Bearer 1'
})
.expect(401, done)
})
it('should fetch all users', (done) => {
const { token } = context
request
.get('/users')
.set({
Accept: 'application/json',
Authorization: `Bearer ${token}`
})
.expect(200, (err, res) => {
if (err) { return done(err) }
res.body.should.have.property('users')
res.body.users.should.have.length(1)
done()
})
})
})
describe('GET /users/:id', () => {
it('should not fetch user if token is invalid', (done) => {
request
.get('/users/1')
.set({
Accept: 'application/json',
Authorization: 'Bearer 1'
})
.expect(401, done)
})
it('should throw 404 if user doesn\'t exist', (done) => {
const { token } = context
request
.get('/users/1')
.set({
Accept: 'application/json',
Authorization: `Bearer ${token}`
})
.expect(404, done)
})
it('should fetch user', (done) => {
const {
user: { _id },
token
} = context
request
.get(`/users/${_id}`)
.set({
Accept: 'application/json',
Authorization: `Bearer ${token}`
})
.expect(200, (err, res) => {
if (err) { return done(err) }
res.body.should.have.property('user')
expect(res.body.user.password).to.not.exist
done()
})
})
})
describe('PUT /users/:id', () => {
it('should not update user if token is invalid', (done) => {
request
.put('/users/1')
.set({
Accept: 'application/json',
Authorization: 'Bearer 1'
})
.expect(401, done)
})
it('should throw 404 if user doesn\'t exist', (done) => {
const { token } = context
request
.put('/users/1')
.set({
Accept: 'application/json',
Authorization: `Bearer ${token}`
})
.expect(404, done)
})
it('should update user', (done) => {
const {
user: { _id },
token
} = context
request
.put(`/users/${_id}`)
.set({
Accept: 'application/json',
Authorization: `Bearer ${token}`
})
.send({ user: { username: 'updatedcoolname' } })
.expect(200, (err, res) => {
if (err) { return done(err) }
res.body.user.should.have.property('username')
res.body.user.username.should.equal('updatedcoolname')
expect(res.body.user.password).to.not.exist
done()
})
})
})
describe('DELETE /users/:id', () => {
it('should not delete user if token is invalid', (done) => {
request
.delete('/users/1')
.set({
Accept: 'application/json',
Authorization: 'Bearer 1'
})
.expect(401, done)
})
it('should throw 404 if user doesn\'t exist', (done) => {
const { token } = context
request
.delete('/users/1')
.set({
Accept: 'application/json',
Authorization: `Bearer ${token}`
})
.expect(404, done)
})
it('should delete user', (done) => {
const {
user: { _id },
token
} = context
request
.delete(`/users/${_id}`)
.set({
Accept: 'application/json',
Authorization: `Bearer ${token}`
})
.expect(200, done)
})
})
})
================================================
FILE: test/utils.js
================================================
import mongoose from 'mongoose'
export function cleanDb () {
for (const collection in mongoose.connection.collections) {
if (mongoose.connection.collections.hasOwnProperty(collection)) {
mongoose.connection.collections[collection].remove()
}
}
}
export function authUser (agent, callback) {
agent
.post('/users')
.set('Accept', 'application/json')
.send({ user: { username: 'test', password: 'pass' } })
.end((err, res) => {
if (err) { return callback(err) }
callback(null, {
user: res.body.user,
token: res.body.token
})
})
}
gitextract_sjp2bf63/
├── .babelrc
├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── bin/
│ └── server.js
├── config/
│ ├── env/
│ │ ├── common.js
│ │ ├── development.js
│ │ ├── production.js
│ │ └── test.js
│ ├── index.js
│ └── passport.js
├── index.js
├── package.json
├── src/
│ ├── middleware/
│ │ ├── index.js
│ │ └── validators.js
│ ├── models/
│ │ └── users.js
│ ├── modules/
│ │ ├── auth/
│ │ │ ├── controller.js
│ │ │ └── router.js
│ │ ├── index.js
│ │ └── users/
│ │ ├── controller.js
│ │ └── router.js
│ └── utils/
│ └── auth.js
└── test/
├── auth.spec.js
├── users.spec.js
└── utils.js
SYMBOL INDEX (11 symbols across 6 files)
FILE: src/middleware/index.js
function errorMiddleware (line 1) | function errorMiddleware () {
FILE: src/middleware/validators.js
function ensureUser (line 6) | async function ensureUser (ctx, next) {
FILE: src/modules/auth/controller.js
function authUser (line 53) | async function authUser (ctx, next) {
FILE: src/modules/users/controller.js
function createUser (line 41) | async function createUser (ctx) {
function getUsers (line 87) | async function getUsers (ctx) {
function getUser (line 119) | async function getUser (ctx, next) {
function updateUser (line 180) | async function updateUser (ctx) {
function deleteUser (line 213) | async function deleteUser (ctx) {
FILE: src/utils/auth.js
function getToken (line 1) | function getToken (ctx) {
FILE: test/utils.js
function cleanDb (line 3) | function cleanDb () {
function authUser (line 11) | function authUser (agent, callback) {
Condensed preview — 27 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
{
"path": ".babelrc",
"chars": 57,
"preview": "{\n \"presets\": [\n \"es2015-node5\",\n \"stage-0\"\n ]\n}\n"
},
{
"path": ".editorconfig",
"chars": 781,
"preview": "# http://editorconfig.org\n\n# A special property that should be specified at the top of the file outside of\n# any section"
},
{
"path": ".eslintrc.json",
"chars": 108,
"preview": "{\n \"parser\": \"babel-eslint\",\n \"extends\": \"standard\",\n \"env\": {\n \"node\": true,\n \"mocha\": true\n }\n}\n"
},
{
"path": ".gitignore",
"chars": 999,
"preview": "\n\n# Created by https://www.gitignore.io/api/node,sublimetext\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime d"
},
{
"path": "LICENSE",
"chars": 1099,
"preview": "The MIT License (MIT)\nCopyright (c) 2016 Adrian Obelmejias <adrian@obel.me>\n\nPermission is hereby granted, free of charg"
},
{
"path": "README.md",
"chars": 2307,
"preview": "#koa2-api-boilerplate\n[](http://"
},
{
"path": "bin/server.js",
"chars": 917,
"preview": "import Koa from 'koa'\nimport bodyParser from 'koa-bodyparser'\nimport convert from 'koa-convert'\nimport logger from 'koa-"
},
{
"path": "config/env/common.js",
"chars": 52,
"preview": "export default {\n port: process.env.PORT || 5000\n}\n"
},
{
"path": "config/env/development.js",
"chars": 148,
"preview": "export default {\n session: 'secret-boilerplate-token',\n token: 'secret-jwt-token',\n database: 'mongodb://localhost:27"
},
{
"path": "config/env/production.js",
"chars": 149,
"preview": "export default {\n session: 'secret-boilerplate-token',\n token: 'secret-jwt-token',\n database: 'mongodb://localhost:27"
},
{
"path": "config/env/test.js",
"chars": 149,
"preview": "export default {\n session: 'secret-boilerplate-token',\n token: 'secret-jwt-token',\n database: 'mongodb://localhost:27"
},
{
"path": "config/index.js",
"chars": 182,
"preview": "import common from './env/common'\n\nconst env = process.env.NODE_ENV || 'development'\nconst config = require(`./env/${env"
},
{
"path": "config/passport.js",
"chars": 827,
"preview": "import passport from 'koa-passport'\nimport User from '../src/models/users'\nimport { Strategy } from 'passport-local'\n\npa"
},
{
"path": "index.js",
"chars": 86,
"preview": "require('babel-core/register')()\nrequire('babel-polyfill')\nrequire('./bin/server.js')\n"
},
{
"path": "package.json",
"chars": 1791,
"preview": "{\n \"name\": \"koa2-api-boilerplate\",\n \"version\": \"2.2.0\",\n \"description\": \"Koa2 boilerplate covering essentials for API"
},
{
"path": "src/middleware/index.js",
"chars": 234,
"preview": "export function errorMiddleware () {\n return async (ctx, next) => {\n try {\n await next()\n } catch (err) {\n "
},
{
"path": "src/middleware/validators.js",
"chars": 511,
"preview": "import User from '../models/users'\nimport config from '../../config'\nimport { getToken } from '../utils/auth'\nimport { v"
},
{
"path": "src/models/users.js",
"chars": 1263,
"preview": "import mongoose from 'mongoose'\nimport bcrypt from 'bcrypt'\nimport config from '../../config'\nimport jwt from 'jsonwebto"
},
{
"path": "src/modules/auth/controller.js",
"chars": 1808,
"preview": "import passport from 'koa-passport'\n\n/**\n * @apiDefine TokenError\n * @apiError Unauthorized Invalid JWT token\n *\n * @api"
},
{
"path": "src/modules/auth/router.js",
"chars": 175,
"preview": "import * as auth from './controller'\n\nexport const baseUrl = '/auth'\n\nexport default [\n {\n method: 'POST',\n route"
},
{
"path": "src/modules/index.js",
"chars": 848,
"preview": "import glob from 'glob'\nimport Router from 'koa-router'\n\nexports = module.exports = function initModules (app) {\n glob("
},
{
"path": "src/modules/users/controller.js",
"chars": 5349,
"preview": "import User from '../../models/users'\n\n/**\n * @api {post} /users Create a new user\n * @apiPermission\n * @apiVersion 1.0."
},
{
"path": "src/modules/users/router.js",
"chars": 706,
"preview": "import { ensureUser } from '../../middleware/validators'\nimport * as user from './controller'\n\nexport const baseUrl = '/"
},
{
"path": "src/utils/auth.js",
"chars": 324,
"preview": "export function getToken (ctx) {\n const header = ctx.request.header.authorization\n if (!header) {\n return null\n }\n"
},
{
"path": "test/auth.spec.js",
"chars": 1280,
"preview": "import app from '../bin/server'\nimport supertest from 'supertest'\nimport { expect, should } from 'chai'\nimport { cleanDb"
},
{
"path": "test/users.spec.js",
"chars": 5673,
"preview": "import app from '../bin/server'\nimport supertest from 'supertest'\nimport { expect, should } from 'chai'\nimport { cleanDb"
},
{
"path": "test/utils.js",
"chars": 604,
"preview": "import mongoose from 'mongoose'\n\nexport function cleanDb () {\n for (const collection in mongoose.connection.collections"
}
]
About this extraction
This page contains the full source code of the adrianObel/koa2-api-boilerplate GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 27 files (27.8 KB), approximately 8.3k tokens, and a symbol index with 11 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.