@dunebird/dune v0.0.4
Dune
More detailed documentation coming soon.
Description
Dune is a CLI tool to create REST APIs using Node.js. It generates project files and implements the following packages:
- express
- knex
- objection
- bcryptjs
- jsonwebtoken
- indicative
- factory-girl
- @sendgrid/mail
- nodemon
- env-cmd
- jest
- supertest
Installation
Install this package globally using the npm install
command.
npm install @dunebird/dune -g
Usage
Getting started
Project creation
Create a project with the new
command followed by the project name.
dune new notes
Create a project with built-in authentication by adding the --auth flag.
dune new notes --auth
Configuration
Configure environment variables by editing the .env
files inside the /config
directory.
Since this tool uses knex, you may want to install one of the following according to your DB_CLIENT
environment variable.
npm install pg
npm install sqlite3
npm install mysql
npm install mysql2
Start server
Start the serve with the serve
command.
dune serve
Start a development server using nodemon by adding the --dev flag.
dune serve --dev
Database
Make sure to create databases according to the values specified with the DB_NAME
variable in the configuration files.
Migrations
Create a new migration by passing the name of the model to the migration:create
command. This will create a new knex migration file inside the /src/db/migrations
directory.
dune migration:create note
Specify the up
and down
instructions using the functions provided by knex Schema Builder.
// /src/db/migrations/XXXXXXXXXXXXX_note.js
exports.up = (knex) => {
return knex.schema.createTable('notes', (table) => {
table.increments('id').primary();
table.integer('user_id').unsigned().notNullable();
table.string('title').notNullable();
table.text('content').notNullable();
});
};
exports.down = (knex) => {
return knex.schema.dropTableIfExists('notes');
};
Run migrations with the migration:run
command, specify environment by using the --env
argument with the value dev
or test
.
dune migration:run --env=dev
Models
Create a new model by passing its name to the model:create
command. A new model file will be created in the /src/models
directory.
dune model:create note
Return the name of the corresponding table in the tableName()
method as specified in the objection models documentation. Specify relationships in the relationMappings()
method (see objection relations documentation).
// /src/models/note.js
const {Model} = require('objection');
class Note extends Model{
static get tableName(){
return 'notes';
}
static get relationMappings(){
const User = require('./user');
return {
user: {
relation: Model.BelongsToOneRelation,
modelClass: User,
join: {
from: 'notes.user_id',
to: 'users.id'
}
}
}
}
}
module.exports = Note;
Factories
Create a new factory by passing the name of the model to the factory:create
command. A new file will be created in the /src/db/factories
directory.
dune factory:create note
Modify the file to define a factory according to the factory-girl documentation.
// /src/db/factories/noteFactory.js
const Note = require('../../models/note');
const noteFactory = (factory) => {
factory.define('note', Note, {
title: factory.chance('sentence'),
content: factory.chance('paragraph')
});
}
module.exports = noteFactory;
Require the new factory inside the /src/db/factories/factory.js
file.
// /src/db/factories/factory.js
const {factory} = require('factory-girl');
const ObjectionAdapter = require('factory-girl-objection-adapter');
require('./userFactory')(factory);
require('./noteFactory')(factory); // new factory
factory.setAdapter(new ObjectionAdapter());
module.exports = factory;
Seeds
Create a new seed by passing the name of the model to the seed:create
command. A new file will be created inside the /src/db/seeds
directory.
dune seed:create note
Enter the seed's code inside the exports.seed
function, don't forget to pass the knex
parameter to the model's knex()
function.
// /src/db/seeds/XXXXXXXXXXXXX_noteSeed.js
const factory = require('../factories/factory');
const Note = require('../../models/note');
const User = require('../../models/user');
exports.seed = async (knex) => {
Note.knex(knex);
const users = await User.query();
for(let i = 0; i < users.length; i++){
await users[i].$relatedQuery('notes').insert(await factory.buildMany('note', 10));
}
};
Run seeds with the seed:run
command, specify environment with the --env
argument with the value dev
or test
.
dune seed:run --env=dev
HTTP
Routers
Create a new router by passing the name of the model to the router:create
command. A new file with some default code will be created in the /src/routers
directory.
dune router:create note
Require and register the router in the /src/app.js
file as follows.
// /src/app.js
const express = require('express');
require('./db/objection.js');
const userRouter = require('./routers/userRouter');
const noteRouter = require('./routers/noteRouter'); // import the router
const app = express();
app.use(express.json());
app.use(userRouter);
app.use(noteRouter); // register the router
app.get('/', (req, res) => {
res.send('it works!')
});
module.exports = app;
Controllers
Create a new controller by passing the name of the model to the controller:create
command. A new file will be created in the /src/controllers
directory.
dune controller:create note
You may now write some code inside one of the controller's functions and link it to a router.
// /src/controllers/noteController.js snippet
exports.index = async (req, res) => {
try{
const notes = await Note.query();
res.send(notes);
}catch(error){
res.status(500).send();
}
};
// /src/routers/noteRouter.js
const express = require('express');
const binder = require('./binder');
const Note = require('../models/note');
const noteController = require('../controllers/noteController'); // require controller
const router = new express.Router();
router.param('note', binder('note', Note));
router.get('/notes', noteController.index); // link route to controller's function
module.exports = router;
As a result, this would produce a collection of objects with the following structure.
{
"id": 2,
"user_id": 1,
"title": "note 2",
"content": "this is another note"
}
Serializers
Serializers are useful to filter properties from models before sending their data to the client. Create a new serializer by passing the name of the model to the serializer:create
command. This will create a new file in the /src/serializers
directory.
dune serializer:create note
You may define different "filters" inside the serialize
function.
// /src/serializers/noteSerializer.js snippet
exports.serialize = (note, option) => {
switch(option){
case 'withoutIds':
return {
title: note.title,
content: note.content
}
break;
default:
return {
id: note.id,
title: note.title,
content: note.content
};
}
};
Now, you may use the serializer in a controller to filter data before sending it to the client. Use the collection
function to serialize an array of model instances.
// /src/controllers/noteController.js
const Note = require('../models/note');
const noteSerializer = require('../serializers/noteSerializer'); // require serializer
exports.index = async (req, res) => {
try{
const notes = await Note.query();
res.send(noteSerializer.collection(notes, 'withoutIds')); // filter data
}catch(error){
res.status(500).send();
}
};
Use the serialize
method to serialize a single model instance.
// /src/controllers/noteController.js snippet
exports.show = async (req, res) => {
try{
const note = await Note.query().findById(req.params.id);
res.send(noteSerializer.serialize(note, 'withoutIds'));
}catch(error){
res.status(500).send();
}
};
The 'withoutIds'
filter shown above as an example will now filter model instances to match the following structure.
{
"title": "note 1",
"content": "this is a note"
}
Validators
Create a new validator by passing its name to the validator:create
command. A new file will be created in the /src/validators
directory.
dune validator:create storeNote
Modify the rules
variable inside the validator to implement rules over the received data. Use the indicative documentation to find the available validation rules and their usage.
// /src/validators/storeNote.js snippet
const rules = {
title: 'required|string',
content: 'required|string'
};
You can now use a validator as a middleware in your routers.
// /src/routers/noteRouter.js snippet
router.post('/notes', storeNote, noteController.store);
Middleware
Create a new middleware by passing its name to the middleware:create
command. A new file will be created in the /src/middleware
directory.
dune middleware:create auth