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

unchained

v0.1.1

Published

A clear MVC structure and some syntax sugar for Express.

Downloads

4

Readme

Express... Unchained

Unchained is a Node.js module which abstracts the underlying Express framework, providing a clear MVC structure for your Node.js projects. Unchained breaks everything into pieces, and maps it all to Express for you. Unchained aims to provide a simple layer of abstraction above Express, and is fully compatible with existing Express modules and middleware.

How's it work?

Unchained takes care of requiring Express for you, as well as pulling together all of your views, models and middleware. To define a view, model, or middleware function, it's as easy as creating a .js file in the appropriate folder. Routes are defined declaratively with a simple dictionary (object literal) inside urls.js. Template rendering is provided out of the box with Swig (Django-style templates). Control all of your Express app's boilerplate settings inside config.js.

A typical project structure starts out with the following:

/middleware
/models
/node_modules
/templates
/views
app.js
config.js
urls.js

Getting Started

Get started by requiring Unchained inside your app.js. Pass in the root module and app directory to allow Unchained to require the rest of your modules:

// app.js
app = require('unchained')(module, __dirname);

urls.js

If you're building an app, you might need to define some routes. In your main app directory, create the urls.js module. Route definitions are stored here as a simple dictionary (Object-literal), with keys defining routes, and values specifying matched controllers:

// urls.js
module.exports = {
    '/': view.Home,
    '/about/': view.About,
    '/profile/': view.Profile,
};

/views, /models, /middleware, oh my

All view, model and middleware components are defined as .js modules in their assigned folder, with the name of the module specifying the name of the component. Modules are automatically namespaced under the globals view, model and m (for middleware).

Views

To create a new view called view.Profile, create a .js file in the /views directory named Profile.js:

// views/Profile.js
// Simple Function-based view
module.exports = function (req, res) {
    res.render('profile'); // Renders /templates/profile.html
};

View definitions can be composed of Functions, Objects or Arrays. The Function-based view above does not specify any specific HTTP method, so by default it matches all HTTP methods. You can override the default HTTP method within config.js. Or, you can just define your view as an Object-literal. With an Object-literal view, you can specify explicit HTTP methods for a given route:

// views/Profile.js
// Object-literal syntax (Explicit HTTP Methods)
module.exports = {
    get: function (req, res) {
        res.render('profile');
    },
    post: function (req, res) {
        // Do something with POST request
        res.render('profile');
    }
};

Middleware in Views

You can assign Route-Specific Middleware directly to your views by wrapping them with an Array, always passing your view object as the last item in the Array. Any number of middleware functions may be passed in this style:

// views/Profile.js
// Array syntax (Route Middleware) -- Maps to all()
module.exports = [m.requireLogin, m.exampleWare, function (req, res) {
    res.render('profile');
}];

Either type of view object can be wrapped with a middleware Array, whether it be a simple Function-based view as above, or an Object-literal view, with multiple HTTP methods defined:

// views/Profile.js
// Wrapping both HTTP methods with Middleware
module.exports = [m.requireLogin, m.exampleWare, {
    get: function (req, res) {
        res.render('profile');
    },
    post: function (req, res) {
        // Do something with POST request
        res.render('profile');;
    }
}];

You can choose to wrap only a specific HTTP method with a middleware Array, instead of the entire view Object (which assigns to each of the methods defined):

// views/Profile.js
// Wrapping a single HTTP method with Middleware
module.exports = {
    get: function (req, res) {
        res.render('profile');
    },
    post: [m.requireLogin, function (req, res) {
        // Do something with POST request
        res.render('profile');
    }]
}];

Unchained also allows nested Middleware definitions within Object-literal views. Notice the view below, wrapped with a middleware Array, and its POST method wrapped again inside. Middleware nested in this style is executed outside-in, so POST requests received by the view will first call requireLogin, then validateInput. GET requests will call only the requireLogin middleware:

// views/Profile.js
// Nested middleware in Object-literal view
module.exports = [m.requireLogin, {
    get: function (req, res) {
        res.render('profile');
    },
    post: [m.validateInput, function (req, res) {
        // Do something with POST request
        res.render('profile');
    }]
}];

Middleware in urls.js

If you prefer, you can declare your route-specific middleware directly in urls.js. The middleware Array syntax is the same, with view objects passed as the last Array item:

// urls.js
// Middleware assigned directly in Routes
module.exports = {
    '/': view.auth('home'),
    '/about/': [m.requireLogin, view.About],
    '/profile/': [m.requireLogin, view.Profile],
    '/login/': {
        get: [m.redirectUser, view.render('login')],
        post: [m.loginUser, view.redirect('/')],
    },
    '/logout/': [m.logoutUser, view.redirect('/login')],
    '/error/(:err_no)?/?': view.Error,
    '*': view.redirect('/error/404/'),
};

You might have noticed a few helper methods above, attached to the view object. The methods view.auth, view.render and view.redirect are actually reusable view Generators, which take in arguments and return customized views. The /about and /profile view definitions above are functionally equivalent to view.auth.

Generator methods can leverage middleware, models, and can be created like normal modules. You can define them inside /views, /models and /middleware, but I recommend storing them in the index.js of their respective folder.

Global Middleware & Bare-Metal Express

You can easily apply urls.js to declare your "global" middleware. But you also have the option to configure your Express app directly, within config.js:

// config.js
// Configuring global middleware and other Express options
module.exports = function (app) {

    app.set('listen_port', 8080);
    app.set('default_method', 'get'); // Default HTTP method for basic view Functions
    app.set('view engine', 'html');
    app.set('views', app.get('root_dir') + '/templates');
    app.engine('html', swig.renderFile);
    app.enable('strict routing');
    app.use(m.addSlashes());
    app.use(express.urlencoded());
    app.use(express.json());
    app.use(express.logger());
    app.use(express.cookieParser());
    app.use(express.methodOverride());
    app.use(express.session({ secret: '.PLEASE_CHANGE-ME*1a2b3c4d5e6f7g8h9i0j!' }));
    app.use(passport.initialize());
    app.use(passport.session());

    return app;
};