npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

xprez

v2.3.0

Published

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

Downloads

22

Readme

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.