hapi Seed Project - MVC / RDMS
This project is an educational application skeleton for a typical hapi RESTful API. You can use it to quickly bootstrap your hapi API projects and be ready to start coding the core of your App within minutes.
- ES6
- MVC file structure + base classes (a'la Rails/Laravel/Django)
- Relational Database support (via knex.js)
- MySQL
- PostgreSQL
- SQLite3
- Schema Migrations and Seeding
- Pre-configured environments (dev, qa, staging, production)
- Powerful payload validations via joi
- Auto-generated documentation (lout)
- Unit & API/REST tests examples (Jasmine2)
- RESTful outputs
- Improved Logging and Remote debugging
- Healthcheck end-point
- Gulp for workflows (ie. watch files changes and launch local server)
- + all features and plugin's from hapi
-
You need git to clone the hapi-seed repository: http://git-scm.com/
-
node.js and npm are needed to execute the code: http://nodejs.org/.
-
A relational database is needed, for instance PostgreSQL:
- Mac: http://postgresapp.com/
- Linux/Windows: http://www.postgresql.org/download/
- Fork this repo (top right button) and clone it form your account (replace
YOUR-USERNAME
)
git clone https://github.com/YOUR-USERNAME/hapijs-seed-mvc.git
cd hapijs-seed-mvc
- Install the dependencies
npm install
- Create a Database. In case of PostgreSQL, go into your psql terminal and enter:
CREATE DATABASE todo;
-
Duplicate
config/dev.js.default
and rename it intoconfig/dev.js
. Then edit and enter your database settings (DB name goes intoconfig/default.js
).
npm run db:migrate
npm run db:seed
- Run the app
export NODE_ENV=dev # only needed once per open terminal
npm start
- Go to: http://localhost:3000
Knex is taking care of migrating the DB schema and populating (seeding) the tables. The documentation is available here: http://knexjs.org/#Migrations-CLI.
Knex will keep a history of your executed migrations and save them into the DB table knex_migrations
.
You have to save the migrations in database/migrations/
. It's recommended to prefix the files with an incrementing number or timestamp. Otherwise it might be hard to keep track of the order the DB changes were executed.
npm run db:migrate
You can rollback the last group of executed migrations:
npm run db:rollback
You can populate your DB tables by executing seed files through Knex. Seed files are saved in database/seeds/
.
npm run db:seed
This project has to kind of tests: UnitTest and API Tests. For both Jasmine2 is being used. If you want to execute both kind of tests at the same time, you can run:
npm test
UnitTest's are stored within the folders of the implementation and contain .spec
as part of their file name. For instance src/controllers/main.controller.js
and src/controllers/main.controller.spec.js
. This pattern makes it easier not to forget to write tests :)
You can execute them by running:
npm run test:unit
API Tests are stored in /tests/api
and are meant to test the RESTful end-points of your App.
In order to test the server responses you have to start the server in a new terminal/tab:
cd /path/to/your/project
export NODE_ENV=dev
npm start
Then execute your API Tests by running:
npm run test:api
The auto-generated API documentation is provided by lout and it's based on the configuration of every route (/routes
).
- Extend the model class: ToDoList (
src/models/ToDoList.js
)
/**
* Constructor
*/
constructor() {
let tableName = 'todo_lists'
super(tableName)
this.ToDo = new ToDo()
}
- Extend the controller class: ToDoListsController
src/controllers/todo_lists.controller.js
/**
* Constructor
*/
constructor() {
let notFoundMsg = `ToDo List not found`
super(notFoundMsg)
this.ToDoList = new ToDoList()
}
/**
* Retrieve the list of all ToDo lists
*/
index(request, reply) {
this.ToDoList.findAll()
.then(reply)
.catch((err) => reply(this.Boom.wrap(err)))
}
/**
* Retrieve a single ToDo list
*/
view(request, reply) {
let id = request.params.id
this.ToDoList.findById(id)
.then((response) => this.replyOnResponse(response, reply))
.catch((err) => reply(this.Boom.wrap(err)))
}
- Export
index()
andview()
end-points insrc/routes/todo_lists.routes.js
//
// Export public end-points
//
export default [
routes.index(),
routes.view()
]
- Lets test it:
curl localhost:3000/todo-lists
curl localhost:3000/todo-lists/1
- Extend the controller class with
create()
,update()
andremove()
: ToDoListsControllersrc/controllers/todo_lists.controller.js
/**
* Create a new ToDo list
*/
create(request, reply) {
let data = request.payload
this.ToDoList.save(data)
.then(reply)
.catch((err) => reply(this.Boom.wrap(err)))
}
/**
* Update an existing ToDo list
*/
update(request, reply) {
let id = request.params.id
, data = request.payload
this.ToDoList.update(id, data)
.then((response) => this.replyOnResponse(response, reply))
.catch((err) => reply(this.Boom.wrap(err)))
}
/**
* Delete a ToDo list
*/
remove(request, reply) {
let id = request.params.id
this.ToDoList.del(id)
.then((response) => this.replyOnResponse(response, reply))
.catch((err) => reply(this.Boom.wrap(err)))
}
- Extend ToDo List routes class with
create()
andupdate()
:src/routes/todo_lists.routes.js
/**
* Create a new ToDo list
*
* @return {object}
*/
create() {
// Get route settings from parent
let route = super.create()
// Update end-point description (used in Documentation)
route.config.description = 'Create a new ToDo list'
// Add validations for POST payload
route.config.validate.payload = {
name: this.joi.string().required().description('ToDo list name')
}
return route
}
/**
* Update an existing ToDo list
*
* @return {object}
*/
update() {
// Get route settings from parent
let route = super.update()
// Update end-point description (used in Documentation)
route.config.description = 'Update an existing ToDo list'
// Add validations for POST payload
route.config.validate.payload = {
name: this.joi.string().description('ToDo list name')
}
return route
}
- Export
create()
,update()
andremove()
end-points insrc/routes/todo_lists.routes.js
//
// Export public end-points
//
export default [
routes.index(),
routes.view(),
routes.create(),
routes.update(),
routes.remove()
]
- Lets test it:
curl localhost:3000/todo-lists
curl -X PUT localhost:3000/todo-lists/1 -d name=Homework
curl -X POST localhost:3000/todo-lists -d name=Uni\ Stuff
curl -X DELETE localhost:3000/todo-lists/1
-
Activate tests for ToDo Lists (
*.spec.js
files) -
Find out how to continue with ToDo's ;)
The configuration allows to setup the TCP debug port for node remote debug functionality (5858 by default). This should be overridden when multiple micro node.js services are running on a local machine in a typical dev environment setup.
Remote debug can be used the command line using node debug or with IDEs supporting this feature.
Deploy your App on a server and you can use forever to run it. forever is used to run your App continuously. It will automatically restart if it crashes.
[sudo] npm install forever -g
cd /path/to/your/project
export NODE_ENV=staging
forever start index.js
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.