Como estruturar um aplicativo express.js?

102

Existe uma convenção comum para dividir e modularizar o app.jsarquivo em um aplicativo Express.js ? Ou é comum manter tudo em um único arquivo?

Eric o Vermelho
fonte
3
Tem havido pessoas dividindo-os em rotas. Além disso, você pode dar uma olhada em express-resources.
BRampersad
@Brandon_R você já experimentou recursos? Olhei para ele e achei que parecia legal, só não chutei os pneus ainda.
Chance de
1
Um pouco tarde, mas recentemente abri o código de um roteador para expresso que permite dividir o app.js muito bem com controladores + visualizações, etc. Veja: github.com/kishorenc/road
jeffreyveon

Respostas:

82

Eu tenho o meu dividido da seguinte forma:

~/app
|~controllers
| |-monkey.js
| |-zoo.js
|~models
| |-monkey.js
| |-zoo.js
|~views
| |~zoos
|   |-new.jade
|   |-_form.jade
|~test
|  |~controllers
|    |-zoo.js
|  |~models
|    |-zoo.js
|-index.js

Eu uso Exportações para retornar o que é relevante. Por exemplo, nos modelos que faço:

module.exports = mongoose.model('PhoneNumber', PhoneNumberSchema);

e se eu precisar criar um número de telefone, é tão simples como:

var PhoneNumber = require('../models/phoneNumber');
var phoneNumber = new PhoneNumber();

se eu precisar usar o esquema, então PhoneNumber.schema

(o que pressupõe que estamos trabalhando na pasta de rotas e precisamos ir 1 nível para cima e depois para baixo para os modelos)


EDITAR 4

O Express wiki possui uma lista de estruturas construídas sobre ele.

Desses, acho que o matador do Twitter está muito bem estruturado. Na verdade, usamos uma abordagem muito semelhante para carregar partes do aplicativo.

derby.js também parece extremamente interessante. É semelhante a meteoro sem todo o hype e realmente dá crédito onde o crédito é devido (notadamente, node e express).


EDITAR 3

Se você é fã do CoffeeScript (eu não sou) e reeeeaaaaaally quer o L&F do Rails, também existe o Tower.js .


EDITAR 2

Se você está familiarizado com Rails e não se importa com o sangramento de alguns conceitos, existe Locomotive . É uma estrutura leve construída no Express. Ele tem uma estrutura muito semelhante ao RoR e carrega alguns dos conceitos mais rudimentares (como roteamento).

Vale a pena conferir, mesmo se você não planeja usá-lo.


EDITAR 1

nodejs-express-mongoose-demo é muito semelhante a como eu estruturei o meu. Confira.

Chance
fonte
2
Para onde vai a lógica de negócios? Você costuma usar ajudantes para coisas como autenticação?
Eric the Red,
@ErictheRed se você está familiarizado com o padrão MVC (rails, Asp.Net mvc, etc), então considero minhas rotas como meus controladores e tudo se encaixa depois disso. A lógica de negócios está nos modelos (embora eu esteja tendo dificuldades com validação e mangusto). Para auxiliares, eu uso o Exports em uma biblioteca de utilitários interna simples que estou montando para as coisas que reutilizo.
Chance de
Seria legal fazer o upload de um exemplo de configuração no github para que possamos olhar. O que vai na pasta / arquivos do Routes?
chovy
1
@chovy Eu adicionei um link para github.com/qed42/nodejs-express-mongoose-demo que tem uma estrutura muito semelhante
Chance
Eu recomendo evitar qualquer framework inchado construído em cima do expresso
Raynos
9

Aviso: referenciar o código que hackeado juntos para nocaute do nó, meio que funciona, mas está longe de ser elegante ou polido.

Para ser mais específico sobre a divisão, app.jstenho o seguinte arquivo app.js

var express = require('express'),
    bootstrap = require('./init/bootstrap.js'),
    app = module.exports = express.createServer();

bootstrap(app);

Isso basicamente significa que coloco todo o meu bootstrap em um arquivo separado e, em seguida, inicializo o servidor.

Então, o que o bootstrap faz?

var configure = require("./app-configure.js"),
    less = require("./watch-less.js"),
    everyauth = require("./config-everyauth.js"),
    routes = require("./start-routes.js"),
    tools = require("buffertools"),
    nko = require("nko"),
    sessionStore = new (require("express").session.MemoryStore)()

module.exports = function(app) {
    everyauth(app);
    configure(app, sessionStore);
    less();
    routes(app, sessionStore);
    nko('/9Ehs3Dwu0bSByCS');


    app.listen(process.env.PORT);
    console.log("server listening on port xxxx");
};

Bem, ele divide toda a configuração de inicialização do servidor em partes agradáveis. Especificamente

  • Eu tenho um chunk que configura toda a minha autenticação OAuth remota usando everyauth.
  • Eu tenho um chunk que configura meu aplicativo (basicamente chamando app.configure)
  • Eu tenho um pouco de código que perfura menos, então ele recompila qualquer um dos meus menos em css em tempo de execução.
  • Eu tenho um código que configura todas as minhas rotas
  • Eu chamo este pequeno módulo nko
  • Finalmente eu inicio o servidor ouvindo uma porta.

Por exemplo, vamos olhar para o arquivo de roteamento

var fs = require("fs"),
    parseCookie = require('connect').utils.parseCookie;

module.exports = function(app, sessionStore) {
    var modelUrl = __dirname + "/../model/",
        models = fs.readdirSync(modelUrl),
        routeUrl = __dirname + "/../route/"
        routes = fs.readdirSync(routeUrl);

Aqui eu carrego todos os meus modelos e rotas como arrays de arquivos.

Aviso de isenção: readdirSync só está ok quando chamado antes de iniciar o servidor http (antes .listen). Chamar chamadas de bloqueio síncronas na hora de início do servidor apenas torna o código mais legível (é basicamente um hack)

    var io = require("socket.io").listen(app);

    io.set("authorization", function(data, accept) {
        if (data.headers.cookie) {
            data.cookie = parseCookie(data.headers.cookie);

            data.sessionId = data.cookie['express.sid'];

            sessionStore.get(data.sessionId, function(err, session) {

                if (err) {
                    return accept(err.message, false);
                } else if (!(session && session.auth)) {
                    return accept("not authorized", false)
                }
                data.session = session;
                accept(null, true);
            });
        } else {
            return accept('No cookie', false);
        }
    });

Aqui eu aperto o socket.io para realmente usar a autorização, em vez de deixar qualquer tom e jack falar com o meu servidor socket.io

    routes.forEach(function(file) {
        var route = require(routeUrl + file),
            model = require(modelUrl + file);

        route(app, model, io);
    });
};

Aqui, começo minhas rotas passando o modelo relevante para cada objeto de rota retornado do arquivo de rota.

Basicamente, o objetivo é você organizar tudo em pequenos módulos legais e então ter algum mecanismo de bootstrapping.

Meu outro projeto (meu blog) tem um arquivo init com uma estrutura semelhante .

Aviso: o blog está quebrado e não constrói, estou trabalhando nisso.

Raynos
fonte
0

Eu tenho meus aplicativos construídos em cima da ferramenta Express-generator. Você pode instalá-lo executando npm install express-generator -ge executando-o usando express <APP_NAME>.

Para lhe dar uma perspectiva, uma das estruturas menores do meu aplicativo era assim:

~/
|~bin
| |-www
|
|~config
| |-config.json
|
|~database
| |-database.js
|
|~middlewares
| |-authentication.js
| |-logger.js
|
|~models
| |-Bank.js
| |-User.js
|
|~routes
| |-index.js
| |-banks.js
| |-users.js
|
|~utilities
| |-fiat-converersion.js
|
|-app.js
|-package.json
|-package-lock.json

Uma coisa legal que gosto nessa estrutura que acabo adotando para qualquer aplicação expressa que desenvolvo é a forma como as rotas são organizadas. Não gostei de ter que exigir cada arquivo de rota no app.js e app.use()cada rota, especialmente à medida que o arquivo fica maior. Como tal, achei útil agrupar e centralizar todos os meus app.use()em um arquivo ./routes/index.js.

No final, meu app.js será parecido com isto:

...
const express = require('express');
const app = express();

...
require('./routes/index')(app);

e meu ./routes/index.js será parecido com isto:

module.exports = (app) => {
  app.use('/users', require('./users'));
  app.use('/banks', require('./banks'));
};

Consigo simplesmente require(./users)porque escrevi a rota dos usuários usando express.Router () que me permite "agrupar" várias rotas e depois exportá-las de uma vez, com o objetivo de tornar a aplicação mais modular.

Este é um exemplo do que você faria na minha rota ./routers/users.js:


const router = require('express').Router();

router.post('/signup', async (req, res) => {
    // Signup code here
});

module.exports = router;

Esperançosamente, isso ajudou a responder sua pergunta! Boa sorte!

JKleinne
fonte