Miss any of our Open RFC calls?Watch the recordings here! »

xprez

2.3.0 • Public • Published

npm downloads npm Build Status Maintainability Known Vulnerabilities License contributions welcome PRs Welcome

Xprez.js

** Notification: This is still a Work in Progress **

A minimal opinionated ES6 web framework with CLI (built on top of Express.js) that separates router, controllers and services.

Heavily inspired by Egg.js and Ruby on Rails.

View Documentation

GitHub Repo

Features

  • ✔︎ Model-View-Controller pattern for your web project
  • ✔︎ Load routes/controllers/services/middlewares automatically for you
  • ✔︎ Access services classes easily
  • ✔︎ Ruby-on-Rails style directory structure
  • ✔︎ CLI program that generates new project templates

Quick Start

To install:

# install globally to use the cli 
$ npm i -g xprez
$ npm i -S xprez

Sample directory is located in ./example. To run the example:

$ git clone https://github.com/yzhan1/xprez.git
cd xprez/ && npm i
cd example/
$ xprez s
$ open http://localhost:3000/users/1

To contribute:

$ git clone https://github.com/yzhan1/xprez.git
cd xprez/ && npm i

Running test suite:

$ npm test

Executable Commands

All commands need to be run in the project directory.

View all options

$ xprez -h

Generate new project

# myapp is the project name 
$ xprez g myapp

This will create ./myapp with the following structure:

myapp/
├── package-lock.json
├── package.json
├── app/
    ├── controllers/
        ├── application.controller.js
        └── // place controllers here
    ├── services/
        └── // place services here
    ├── views/
        └── index.ejs
    ├── middlewares/
        └── // place middlewares here
    ├── models/
    ├── utils/
        └── // place utilities here
    └── public/
├── config/
    ├── environments/
        ├── development.js // development config vars
        ├── test.js        // test config vars
        └── production.js  // prod config vars
    ├── server.js          // App's entry point
    ├── application.js     // App definition
    └── routes.js
└── test/
    ├── controllers/       // controller tests
    ├── services/          // service tests
    └── utils/             // utility tests

Notice that you need to strictly follow this structure in order to make your app executable.

To run the app server

# in ./myapp 
$ xprez s

Generate new controller/service/util

First cd myapp and run the following command

# user is the controller name, -c is for --controller 
$ xprez g user -c

This generates myapp/app/controllers/user.controller.js.

# post is the service name, -s is for --service 
$ xprez g post -s

This generates myapp/app/services/post.service.js.

# math is the utility class name, -u is for --utility 
$ xprez g math -u

This generates myapp/app/utils/math.util.js.

All generate commands need to be executed in the root of the project folder, otherwise it will throw file not found error.

Documentation

The main components are inside app/ and config/ folders. App folder contains key components including controllers, services, views and models. Config folder includes files with config variables, routes declaration and server entry file.

config/application.js

This file includes the application initialization code.

import { App as Application } from 'xprez';
 
const app = new Application({
  baseDir: __dirname,
 
  // middlewares before request
  beforeMiddlewares: [],
  // middlewares after request
  afterMiddlewares: [],
  // bind references in this hash
  binds: {
    redis: new RedisClient(),
    db: new SQLClient()
  }
});
// you can choose to configure a view engine, or just send json as response
app.set('view engine', 'ejs');
 
// expose app for testing
export default app;

You can pass in a hash to the constructor to bind config/database connections or other stuffs to the app object. They can be used in controller and service classes later.

config/server.js

Define server startup logic here.

import app from './application';
// you can use cluster library to start the app here
app.listen(app.config.port);
export default app;

config/routes.js

This file is mainly used to describe the corresponding relationship between the request URL and the controller that processes the request.

A basic router looks like:

// config/routes.js
export default (app) => {
  const { routes, controllers } = app;
  routes.get('/users/:id', controllers.user.show);
};

Then we need a basic implementation of UserController.

// app/controllers/user.controller.js
import ApplicationController from './application.controller';
 
export default class UserController extends ApplicationController {
  show(req, res) {
    res.send('Hello');
  }
}

routes is actually an Express router instance, so you can also use it like how you normally would in a pure Express app.

config/environments

Environments folder contain environment variables that you could like to configure for your app. These variables will be available in the controller and service classes.

Xprez.js will only load the config file based on the environment. So in dev environment, it only loads development.js.

app/controllers

Controllers are responsible for handling each request and sending response to the client. You can generate a controller class using xprez g <controllerName> -c.

A basic controller implementation looks like this:

// app/controllers/user.controller.js
import ApplicationController from './application.controller';
 
// extend from ApplicationController to get binds defined for controller layer only
export default class UserController extends ApplicationController {
  async show(req, res) {
    const uid = req.params.id;
    const user = await this.services.user.findById(uid);
    const two = this.utils.math.addOne(1);
    this.redis.set(uid, user);
    const { language } = this.config;
    res.render('users/show', { user, language });
  }
}

Notice that the controller object has access to our services, utils, configuration and the custom binds we declared in config/application.js.

app/services

Service is a layer used to encapsulate business logic in complex business circumstances. You can generate a service class using xprez g <serviceName> -s.

A basic service implementation:

// app/services/user.service.js
import { Service } from 'xprez';
 
export default class UserService extends Service {
  async findById(uid) {
    const two = this.utils.math.addOne(1);
    const user = await this.db.findById(uid);
    user.language = this.config.language;
    const posts = this.services.post.get(uid);
    return { user, posts };
  }
}

Service classes also have access to config/utils/binds/other services. It's recommended to put all business logic inside service classes so they can be accessed by other controllers/services.

app/middlewares

Middlewares are some functions you would like to execute before or after each request. You can add middlewares inside app/middlewares.

Below is a basic implementation of middleware:

// app/middlewares/greet.js
export default (req, res, next) => {
  console.log('Hello!');
  next();
};

Then, in config/application.js, add this to either beforeMiddlewares or afterMiddlewares depending on whether you want it to run before or after request.

const app = new Application({
  // ...
  beforeMiddlewares: [
    // use only the file name before ".js"
    'greet'
  ],
  afterMiddlewares: [
    'greet'
  ],
  // ...
});

Or you can access your middlewares in config/routes.js by using app.middlewares.greet.

If you have multiple middlewares greet, prompt and farwell, you can list them in the array in the based on the desired execution sequence. For example:

beforeMiddlewares: [
  'greet', 'prompt', 'farwell'
]

Example can be found in ./example/config/application.js.

app/utils

Utililities are mainly stateless helper functions that you want to use throughout your project. You can generate a utility class by doing xprez g <utilityName> -u.

Utility files usually look like this:

// app/utils/math.util.js
export default {
  addOne(x) {
    return x + 1;
  }
};

Then, in your Controller or Service, you can access them by using this.utils.math.addOne(x).

Extensions

Since the Application class is an Express app under the hood, you can still treat it like a normal Express.js app, which means you can add in models, middlewares, authentications and other plugins/libraries as you normally would.

To extend a Controller or Service, all you need to do is add the constructor declaration by doing:

/* same pattern can be used for Controller class as well */
export default class UserService extends Service {
  constructor(app) {
    super(app); // must call super(app) to bind other references
    this.logger = require('log4js').getLogger();
  }
}

Testing

You can test the application like how you test an Express app. A starting test script would look like:

// test/controllers/user.test.js
require = require('esm')(module);
 
const request = require('supertest');
const app = require('path/to/config/application.js').default;
 
describe('Test user.controller.js', () => {
  it('should return 200', (done) => {
    request(app)
      .get('/users/1')
      .expect(200, done);
  });
});

Then you can access the app instance and test it using request libraries.

Remember to set NODE_ENV to test when testing so that the server won't actually start! It's recommended to add your test scripts to package.json as an npm script. It's also recommended to use supertest for testing, but theoretically you can use any libraries you like.

Future Improvements

  • Database/Model support for MySQL/PostgreSQL/MongoDB

License

Xprez.js is released under the MIT License.

Install

npm i xprez

DownloadsWeekly Downloads

1

Version

2.3.0

License

MIT

Unpacked Size

30.6 kB

Total Files

33

Last publish

Collaborators

  • avatar