ExpressJS Como estruturar um aplicativo?


527

Estou usando a estrutura da web ExpressJS para NodeJS.

As pessoas que usam ExpressJS colocar seus ambientes (desenvolvimento, produção, teste ...), suas rotas etc no app.js. Eu acho que não é uma maneira bonita, porque quando você tem um grande aplicativo, o app.js é muito grande!

Eu gostaria de ter esta estrutura de diretórios:

| my-application
| -- app.js
| -- config/
     | -- environment.js
     | -- routes.js

Aqui está o meu código:

app.js

var express = require('express');
var app = module.exports = express.createServer();

require('./config/environment.js')(app, express);
require('./config/routes.js')(app);

app.listen(3000);

config / environment.js

module.exports = function(app, express){
    app.configure(function() {
    app.use(express.logger());
    });

    app.configure('development', function() {
    app.use(express.errorHandler({
        dumpExceptions: true,
        showStack: true
    }));
    });

    app.configure('production', function() {
    app.use(express.errorHandler());
    });
};

config / routes.js

module.exports = function(app) {
    app.get('/', function(req, res) {
    res.send('Hello world !');
    });
};

Meu código funciona bem e acho que a estrutura dos diretórios é bonita. No entanto, o código teve que ser adaptado e não tenho certeza de que seja bom / bonito.

É melhor usar minha estrutura de diretórios e adaptar o código ou simplesmente usar um arquivo (app.js)?

Obrigado por seus conselhos!


Os problemas de desempenho dessa maneira ainda estão à espreita? Lembro-me de ler em algum lugar (talvez o grupo expresso) que quando você separa tudo assim, perde uma tonelada de desempenho. Algo como o seu reqs / s cairá em uma quantidade notável, quase como se fosse um bug.
AntelopeSalad

2
Era do grupo Express do Google. Aqui está o link: groups.google.com/group/express-js/browse_thread/thread/…
AntelopeSalad

52
Não, isso é muito falso
tjholowaychuk

Respostas:


306

OK, já faz um tempo e essa é uma pergunta popular, então criei um repositório de github para andaimes com código JavaScript e um README longo sobre como eu gosto de estruturar um aplicativo express.js de tamanho médio.

focusaurus / express_code_structure é o repositório com o código mais recente para isso. Solicitações pull bem-vindas.

Aqui está um instantâneo do README, já que o stackoverflow não gosta de respostas apenas por um link. Farei algumas atualizações, pois este é um novo projeto que continuarei atualizando, mas no final o repositório do github será o local atualizado para essas informações.


Estrutura do código expresso

Este projeto é um exemplo de como organizar um aplicativo Web express.js de tamanho médio.

Atual para pelo menos expressar v4.14 de dezembro de 2016

Status da compilação

Estilo padrão js

Qual é o tamanho da sua aplicação?

Os aplicativos da Web não são todos iguais e, na minha opinião, não existe uma única estrutura de código que deva ser aplicada a todos os aplicativos express.js.

Se seu aplicativo for pequeno, você não precisará de uma estrutura de diretórios tão profunda como exemplificada aqui. Apenas mantenha a simplicidade e cole alguns .jsarquivos na raiz do seu repositório e pronto. Voilà.

Se seu aplicativo for enorme, em algum momento você precisará dividi-lo em pacotes npm distintos. Em geral, a abordagem node.js parece favorecer muitos pacotes pequenos, pelo menos para bibliotecas, e você deve criar seu aplicativo usando vários pacotes npm, pois isso começa a fazer sentido e justificar a sobrecarga. Assim, à medida que seu aplicativo cresce e parte do código se torna claramente reutilizável fora do aplicativo ou é um subsistema claro, mova-o para seu próprio repositório git e faça-o em um pacote npm independente.

Portanto, o foco deste projeto é ilustrar uma estrutura viável para um aplicativo de tamanho médio.

Qual é a sua arquitetura geral

Existem muitas abordagens para criar um aplicativo Web, como

  • MVC do lado do servidor no Ruby on Rails
  • Estilo de aplicativo de página única no MongoDB / Express / Angular / Node (MEAN)
  • Site básico com alguns formulários
  • Modelos / Operações / Vistas / Eventos no estilo MVC está morto, é hora de seguir em frente
  • e muitos outros atuais e históricos

Cada um deles se encaixa perfeitamente em uma estrutura de diretórios diferente. Para os fins deste exemplo, é apenas um andaime e não um aplicativo totalmente funcional, mas estou assumindo os seguintes pontos principais da arquitetura:

  • O site possui algumas páginas / modelos estáticos tradicionais
  • A parte "aplicativo" do site é desenvolvida como um estilo de aplicativo de página única
  • O aplicativo expõe uma API de estilo REST / JSON ao navegador
  • O aplicativo modela um domínio comercial simples, neste caso, é um aplicativo de concessionária de carros

E o Ruby on Rails?

Será um tema ao longo deste projeto que muitas das idéias incorporadas no Ruby on Rails e as decisões da "Convenção sobre configuração" que eles adotaram, embora amplamente aceitas e usadas, não são realmente muito úteis e às vezes são o oposto do que esse repositório recomenda.

Meu ponto principal aqui é que existem princípios subjacentes à organização do código e, com base nesses princípios, as convenções Ruby on Rails fazem sentido (principalmente) para a comunidade Ruby on Rails. No entanto, apenas imitar essas convenções sem pensar perde o objetivo. Depois de conhecer os princípios básicos, TODOS os seus projetos serão bem organizados e claros: shell scripts, jogos, aplicativos móveis, projetos corporativos e até o diretório inicial.

Para a comunidade Rails, eles desejam ter um único desenvolvedor de Rails alternando de aplicativo para aplicativo e se familiarizar e se familiarizar com ele a cada vez. Isso faz muito sentido se você tiver 37 sinais ou o Pivotal Labs e tiver benefícios. No mundo JavaScript do lado do servidor, o ethos geral é apenas muito mais a oeste, e não temos realmente nenhum problema com isso. É assim que nós fazemos. Estamos acostumados a isso. Mesmo no express.js, é um parente próximo do Sinatra, não do Rails, e aceitar convenções do Rails geralmente não ajuda em nada. Eu diria até Princípios sobre Convenção sobre Configuração .

Princípios e motivações subjacentes

  • Seja mentalmente gerenciável
    • O cérebro só pode lidar e pensar em um pequeno número de coisas relacionadas ao mesmo tempo. É por isso que usamos diretórios. Isso nos ajuda a lidar com a complexidade, concentrando-se em pequenas porções.
  • Seja apropriado ao tamanho
    • Não crie "Diretórios de mansões" onde há apenas 1 arquivo sozinho e 3 diretórios desativados. Você pode ver isso acontecendo nas Ansible Best Practices, que permite que pequenos projetos criem mais de 10 diretórios para armazenar mais de 10 arquivos quando um diretório com três arquivos seria muito mais apropriado. Você não dirige um ônibus para trabalhar (a menos que seja um motorista de ônibus, mas mesmo assim seu ônibus AT não funciona), portanto, não crie estruturas de sistema de arquivos que não sejam justificadas pelos arquivos reais dentro deles .
  • Seja modular, mas pragmático
    • A comunidade de nós em geral favorece pequenos módulos. Qualquer coisa que possa ser totalmente separada do seu aplicativo deve ser extraída em um módulo para uso interno ou publicado publicamente no npm. No entanto, para os aplicativos de tamanho médio que são o escopo aqui, a sobrecarga disso pode adicionar tédio ao seu fluxo de trabalho sem valor proporcional. Portanto, para o momento em que você tiver algum código que é fatorado, mas não o suficiente para justificar um módulo npm completamente separado, considere-o um " proto-módulo " com a expectativa de que, quando ultrapassar algum limite de tamanho, ele será extraído.
    • Algumas pessoas, como @ hij1nx , incluem um app/node_modulesdiretório e têm package.jsonarquivos nos diretórios proto-module para facilitar essa transição e agir como um lembrete.
  • Seja fácil de localizar o código
    • Dado um recurso a ser construído ou um bug a ser corrigido, nosso objetivo é que o desenvolvedor não tenha dificuldade em localizar os arquivos de origem envolvidos.
    • Os nomes são significativos e precisos
    • o código sujo é totalmente removido, não deixado em um arquivo órfão ou apenas comentado
  • Seja amigo da pesquisa
    • todo o código fonte original está no appdiretório para que você possa cdexecutar find / grep / xargs / ag / ack / etc e não se distrair com correspondências de terceiros
  • Use nomeação simples e óbvia
    • Agora, o npm parece exigir nomes de pacotes com todas as letras minúsculas. Acho isso péssimo, mas devo seguir o rebanho; portanto, os nomes de arquivos devem ser usados, kebab-casemesmo que o nome da variável no JavaScript deva ser camelCaseporque- é um sinal de menos no JavaScript.
    • nome da variável corresponde ao nome da base do caminho do módulo, mas com kebab-casetransformado emcamelCase
  • Agrupar por acoplamento, não por função
    • Este é um grande afastamento da convenção do Ruby on Rails de app/views, app/controllers, app/models, etc
    • Os recursos são adicionados a uma pilha completa, por isso quero focar em uma pilha completa de arquivos relevantes para o meu recurso. Ao adicionar um campo de número de telefone ao modelo do usuário, não me importo com nenhum outro controlador que não seja o controlador do usuário e não me importo com nenhum outro modelo que não seja o modelo do usuário.
    • Portanto, em vez de editar 6 arquivos, cada um em seu próprio diretório, e ignorar vários outros arquivos nesses diretórios, este repositório é organizado de modo que todos os arquivos necessários para criar um recurso sejam colocados em um local
    • Pela natureza do MVC, a visualização do usuário é acoplada ao controlador do usuário, que é associado ao modelo do usuário. Portanto, quando eu mudo o modelo do usuário, esses três arquivos geralmente mudam juntos, mas o controlador de negócios ou o controlador do cliente são dissociados e, portanto, não estão envolvidos. O mesmo se aplica a projetos que não são do MVC, geralmente também.
    • A dissociação no estilo MVC ou MOVE em termos de qual código entra em qual módulo ainda é incentivado, mas espalhar os arquivos MVC em diretórios irmãos é apenas irritante.
    • Assim, cada um dos meus arquivos de rotas possui a parte das rotas que possui. Um routes.rbarquivo no estilo trilhos é útil se você deseja ter uma visão geral de todas as rotas no aplicativo, mas ao criar recursos e corrigir bugs, você se preocupa apenas com as rotas relevantes para a peça que está sendo alterada.
  • Armazene testes ao lado do código
    • Esta é apenas uma instância de "agrupar por acoplamento", mas eu queria chamá-lo especificamente. Eu escrevi muitos projetos em que os testes residem em um sistema de arquivos paralelo chamado "tests" e agora que comecei a colocar meus testes no mesmo diretório que o código correspondente, nunca mais voltarei. Isso é mais modular e muito mais fácil de trabalhar nos editores de texto e alivia muitas besteiras do caminho "../../ ..". Em caso de dúvida, tente em alguns projetos e decida por si mesmo. Não vou fazer nada além disso para convencê-lo de que é melhor.
  • Reduza o acoplamento transversal com eventos
    • É fácil pensar "OK, sempre que um novo negócio é criado, quero enviar um email para todos os vendedores" e, em seguida, basta colocar o código para enviar esses emails na rota que cria negócios.
    • No entanto, esse acoplamento acabará transformando seu aplicativo em uma gigantesca bola de barro.
    • Em vez disso, o DealModel deve apenas disparar um evento "create" e não ter consciência do que mais o sistema pode fazer em resposta a isso.
    • Quando você codifica dessa maneira, torna-se muito mais possível inserir todo o código relacionado ao usuário, app/usersporque não existe um ninho de ratos de lógica de negócios acoplada em todo o lugar, poluindo a pureza da base de códigos do usuário.
  • O fluxo de código é seguível
    • Não faça coisas mágicas. Não carregue automaticamente arquivos de diretórios mágicos no sistema de arquivos. Não seja Rails. O aplicativo inicia em app/server.js:1e você pode ver tudo o que carrega e executa seguindo o código.
    • Não faça DSLs para suas rotas. Não faça metaprogramações tolas quando não for necessário.
    • Se seu aplicativo é tão grande que fazer magicRESTRouter.route(somecontroller, {except: 'POST'})é uma grande vitória para você mais de 3 básico app.get, app.put, app.del, chamadas, provavelmente você está criando um aplicativo monolítico que é muito grande para trabalhar eficazmente diante. Seja extravagante por grandes vitórias, não pela conversão de 3 linhas simples em 1 linha complexa.
  • Use nomes de arquivos com letras minúsculas no kebab

    • Esse formato evita problemas de distinção entre maiúsculas e minúsculas do sistema de arquivos entre plataformas
    • O npm proíbe maiúsculas em novos nomes de pacotes, e isso funciona bem com isso

      express.js específicos

  • Não use app.configure. É quase totalmente inútil e você simplesmente não precisa. É um monte de boilerplate devido a copypasta irracional.

  • A ORDEM DE MIDDLEWARE E ROTAS EM MATÉRIA EXPRESSA !!!
    • Quase todos os problemas de roteamento que vejo no stackoverflow são middleware expresso fora de ordem
    • Em geral, você deseja que suas rotas sejam desacopladas e não confie na ordem
    • Não use app.usepara todo o aplicativo se você realmente só precisa desse middleware para 2 rotas (estou olhando para você body-parser)
    • Certifique-se de que, quando tudo estiver dito e feito, você EXATAMENTE esta ordem:
      1. Qualquer middleware importante para toda a aplicação
      2. Todas as suas rotas e middlewares de rotas variadas
      3. ENTÃO manipuladores de erro
  • Infelizmente, sendo inspirado no sinatra, o express.js geralmente assume que todas as suas rotas estarão dentro server.jse ficará claro como elas serão ordenadas. Para um aplicativo de tamanho médio, é bom dividir as coisas em módulos de rotas separadas, mas introduz o perigo do middleware fora de ordem

O truque de link simbólico do aplicativo

Existem muitas abordagens descritas e discutidas em profundidade pela comunidade na grande essência Melhor locais exigem caminhos () para Node.js . Em breve, posso decidir preferir "apenas lidar com lotes de ../../../ .." ou usar o modulo requireFrom. No entanto, no momento, eu tenho usado o truque de link simbólico detalhado abaixo.

Portanto, uma maneira de evitar o intraprojeto exige caminhos relativos irritantes, como require("../../../config")é usar o seguinte truque:

  • crie um link simbólico em node_modules para seu aplicativo
    • cd node_modules && ln -nsf ../app
  • adicione apenas o link simbólico node_modules / app , não toda a pasta node_modules, ao git
    • git add -f node_modules / app
    • Sim, você ainda deve ter "node_modules" em seu .gitignorearquivo
    • Não, você não deve colocar "node_modules" em seu repositório git. Algumas pessoas recomendam que você faça isso. Eles estão incorretos.
  • Agora você pode exigir módulos intraprojetos usando esse prefixo
    • var config = require("app/config");
    • var DealModel = require("app/deals/deal-model");
  • Basicamente, isso faz com que o intraprojeto exija um trabalho muito semelhante ao exigido para módulos npm externos.
  • Desculpe, usuários do Windows, você precisa seguir os caminhos relativos do diretório pai.

Configuração

Geralmente codifique módulos e classes para esperar apenas a optionspassagem de um objeto JavaScript básico . Somente o app/server.jscarregamento deve ser carregado app/config.js. A partir daí, ele pode sintetizar optionsobjetos pequenos para configurar subsistemas, conforme necessário, mas o acoplamento de cada subsistema a um grande módulo de configuração global cheio de informações extras é um acoplamento ruim.

Tente centralizar a criação de conexões com o banco de dados e passe-as para os subsistemas, em vez de passar os parâmetros de conexão e fazer com que os subsistemas façam as próprias conexões de saída.

NODE_ENV

Essa é outra idéia atraente, mas terrível, herdada do Rails. Deve haver exatamente um lugar no seu aplicativo, app/config.jsque analisa a NODE_ENVvariável de ambiente. Tudo o resto deve ter uma opção explícita como argumento do construtor de classe ou parâmetro de configuração do módulo.

Se o módulo de e-mail tiver uma opção de como enviar e-mails (SMTP, log para stdout, colocar na fila etc.), deve ser uma opção como {deliver: 'stdout'}essa, mas absolutamente não deve ser verificada NODE_ENV.

Testes

Agora, mantenho meus arquivos de teste no mesmo diretório que o código correspondente e uso as convenções de nomenclatura de extensão de nome de arquivo para distinguir testes de código de produção.

  • foo.js tem o código do módulo "foo"
  • foo.tape.js possui os testes baseados em nós para foo e vive no mesmo diretório
  • foo.btape.js pode ser usado para testes que precisam ser executados em um ambiente de navegador

Uso globs do sistema de arquivos e o find . -name '*.tape.js'comando para obter acesso a todos os meus testes, conforme necessário.

Como organizar o código dentro de cada .jsarquivo de módulo

O escopo deste projeto é principalmente para onde os arquivos e diretórios vão, e não quero adicionar muito outro escopo, mas mencionarei apenas que organizo meu código em três seções distintas.

  1. O bloco de abertura do CommonJS requer chamadas para dependências de estado
  2. Bloco de código principal de JavaScript puro. Nenhuma poluição do CommonJS aqui. Não faça referência a exportações, módulos ou exigências.
  3. Bloco de fechamento do CommonJS para configurar exportações

1
O que devo usar em vez do bodyParser Se eu tiver apenas algumas rotas que o usam?
precisa saber é o seguinte

3
Eu encontrei o que eu estava procurando aqui: stackoverflow.com/questions/12418372/...
Ilan Frumer

1
@wlingke, visite gist.github.com/branneman/8048520 para obter uma discussão completa das abordagens disponíveis para esse problema.
Peter Lyons

@peterLyons Obrigado por compartilhar isso. Depois de ler, acho que vou escrever um script de inicialização. Obrigado!
wlingke

2
com relação ao truque O link simbólico aplicativo , há este pequeno módulo que faz com que todos os problemas vão embora
Hayko koryun

157

UPDATE (29/10/2013) : consulte também minha outra resposta, que possui JavaScript em vez de CoffeeScript pela demanda popular, além de um repositório do github padrão e um README extenso detalhando minhas recomendações mais recentes sobre este tópico.

Config

O que você está fazendo está bem. Eu gosto de ter meu próprio namespace de configuração configurado em um config.coffeearquivo de nível superior com um namespace aninhado como este.

#Set the current environment to true in the env object
currentEnv = process.env.NODE_ENV or 'development'
exports.appName = "MyApp"
exports.env =
  production: false
  staging: false
  test: false
  development: false
exports.env[currentEnv] = true
exports.log =
  path: __dirname + "/var/log/app_#{currentEnv}.log"
exports.server =
  port: 9600
  #In staging and production, listen loopback. nginx listens on the network.
  ip: '127.0.0.1'
if currentEnv not in ['production', 'staging']
  exports.enableTests = true
  #Listen on all IPs in dev/test (for testing from other machines)
  exports.server.ip = '0.0.0.0'
exports.db =
  URL: "mongodb://localhost:27017/#{exports.appName.toLowerCase()}_#{currentEnv}"

Isso é amigável para a edição do sysadmin. Então, quando eu preciso de algo, como as informações de conexão com o banco de dados, é

require('./config').db.URL

Rotas / Controladores

Eu gosto de deixar minhas rotas com meus controladores e organizá-las em um app/controllerssubdiretório. Então eu posso carregá-los e deixá-los adicionar as rotas necessárias.

No meu app/server.coffeearquivo coffeescript eu faço:

[
  'api'
  'authorization'
  'authentication'
  'domains'
  'users'
  'stylesheets'
  'javascripts'
  'tests'
  'sales'
].map (controllerName) ->
  controller = require './controllers/' + controllerName
  controller.setup app

Então, eu tenho arquivos como:

app/controllers/api.coffee
app/controllers/authorization.coffee
app/controllers/authentication.coffee
app/controllers/domains.coffee

E, por exemplo, no meu controlador de domínios, tenho uma setupfunção como esta.

exports.setup = (app) ->
  controller = new exports.DomainController
  route = '/domains'
  app.post route, controller.create
  app.put route, api.needId
  app.delete route, api.needId
  route = '/domains/:id'
  app.put route, controller.loadDomain, controller.update
  app.del route, controller.loadDomain, exports.delete
  app.get route, controller.loadDomain, (req, res) ->
    res.sendJSON req.domain, status.OK

Visualizações

Colocar pontos de vista app/viewsestá se tornando o local habitual. Eu coloco isso assim.

app/views/layout.jade
app/views/about.jade
app/views/user/EditUser.jade
app/views/domain/EditDomain.jade

Arquivos estáticos

Vá em um publicsubdiretório.

Github / Semver / NPM

Coloque um arquivo de marcação README.md na raiz do seu repositório git para github.

Coloque um arquivo package.json com um número de versão semântica no seu git repo root para o NPM.


1
Ei Peter! Eu realmente gosto dessa abordagem que você está adotando. Estou trabalhando na construção de um projeto expresso e eu realmente gostaria de fazer as coisas da maneira certa do que apenas hackear e divulgar. Seria excelente se você tivesse um repositório de amostra no github e / ou uma postagem no blog.
suVasH .....

4
Este repo tem um monte de padrões que você pode usar como referência: github.com/focusaurus/peterlyons.com
Peter Lyons

75
Os scripts de café dificultam a leitura: / Alguma chance de obter uma edição JS de baunilha? Obrigado
toasted_flakes

1
Obrigado por esta resposta. Eu só estou tentando entender isso. Como você acessa os outros controladores de dentro de outro (por exemplo, na função de configuração como o acimaapp.put route, api.needId
chmanie

@ PeterLyons: ei cara, eu vi o seu código-fonte, mas não tenho idéia de como fazer o modo de compilação, eu já instalei Goe incluí o binarquivo na estrutura. Como você executa esse goarquivo bin?
usar o seguinte comando

51

A seguir, é apresentada a resposta de Peter Lyons, literalmente, portada para baunilha JS do Coffeescript, conforme solicitado por vários outros. A resposta de Peter é muito capaz, e qualquer pessoa que vote na minha resposta deve votar nela também.


Config

O que você está fazendo está bem. Eu gosto de ter meu próprio namespace de configuração configurado em um config.jsarquivo de nível superior com um namespace aninhado como este.

// Set the current environment to true in the env object
var currentEnv = process.env.NODE_ENV || 'development';
exports.appName = "MyApp";
exports.env = {
  production: false,
  staging: false,
  test: false,
  development: false
};  
exports.env[currentEnv] = true;
exports.log = {
  path: __dirname + "/var/log/app_#{currentEnv}.log"
};  
exports.server = {
  port: 9600,
  // In staging and production, listen loopback. nginx listens on the network.
  ip: '127.0.0.1'
};  
if (currentEnv != 'production' && currentEnv != 'staging') {
  exports.enableTests = true;
  // Listen on all IPs in dev/test (for testing from other machines)
  exports.server.ip = '0.0.0.0';
};
exports.db {
  URL: "mongodb://localhost:27017/#{exports.appName.toLowerCase()}_#{currentEnv}"
};

Isso é amigável para a edição do sysadmin. Então, quando eu preciso de algo, como as informações de conexão com o banco de dados, é

require('./config').db.URL

Rotas / Controladores

Eu gosto de deixar minhas rotas com meus controladores e organizá-las em um app/controllerssubdiretório. Então eu posso carregá-los e deixá-los adicionar as rotas necessárias.

No meu app/server.jsarquivo javascript eu faço:

[
  'api',
  'authorization',
  'authentication',
  'domains',
  'users',
  'stylesheets',
  'javascripts',
  'tests',
  'sales'
].map(function(controllerName){
  var controller = require('./controllers/' + controllerName);
  controller.setup(app);
});

Então, eu tenho arquivos como:

app/controllers/api.js
app/controllers/authorization.js
app/controllers/authentication.js
app/controllers/domains.js

E, por exemplo, no meu controlador de domínios, eu tenho um setupfunção como esta.

exports.setup = function(app) {
  var controller = new exports.DomainController();
  var route = '/domains';
  app.post(route, controller.create);
  app.put(route, api.needId);
  app.delete(route, api.needId);
  route = '/domains/:id';
  app.put(route, controller.loadDomain, controller.update);
  app.del(route, controller.loadDomain, function(req, res){
    res.sendJSON(req.domain, status.OK);
  });
}

Visualizações

Colocar visualizações em app/viewsestá se tornando o local habitual. Eu coloco isso assim.

app/views/layout.jade
app/views/about.jade
app/views/user/EditUser.jade
app/views/domain/EditDomain.jade

Arquivos estáticos

Vá em um publicsubdiretório.

Github / Semver / NPM

Coloque um arquivo de marcação README.md na raiz do seu repositório git para github.

Coloque um arquivo package.json com um número de versão semântica no seu git repo root para o NPM.


43

Minha pergunta foi introduzida em abril de 2011, é muito antiga. Durante esse período, pude melhorar minha experiência com o Express.js e como arquitetar um aplicativo escrito usando esta biblioteca. Então, compartilho aqui minha experiência.

Aqui está a minha estrutura de diretórios:

├── app.js   // main entry
├── config   // The configuration of my applications (logger, global config, ...)
├── models   // The model data (e.g. Mongoose model)
├── public   // The public directory (client-side code)
├── routes   // The route definitions and implementations
├── services // The standalone services (Database service, Email service, ...)
└── views    // The view rendered by the server to the client (e.g. Jade, EJS, ...)

App.js

O objetivo do app.jsarquivo é inicializar o aplicativo expressjs. Carrega o módulo de configuração, o módulo de logger, aguarda a conexão com o banco de dados, ... e executa o servidor expresso.

'use strict';
require('./config');
var database = require('./services/database');
var express = require('express');
var app = express();
module.exports = app;

function main() {
  var http = require('http');

  // Configure the application.
  app.configure(function () {
    // ... ... ...
  });
  app.configure('production', function () {
    // ... ... ...
  });
  app.configure('development', function () {
    // ... ... ...
  });

  var server = http.createServer(app);

  // Load all routes.
  require('./routes')(app);

  // Listen on http port.
  server.listen(3000);
}

database.connect(function (err) {
  if (err) { 
    // ...
  }
  main();
});

rotas /

O diretório de rotas possui um index.jsarquivo. Seu objetivo é introduzir um tipo de mágica para carregar todos os outros arquivos dentro do routes/diretório. Aqui está a implementação:

/**
 * This module loads dynamically all routes modules located in the routes/
 * directory.
 */
'use strict';
var fs = require('fs');
var path = require('path');

module.exports = function (app) {
  fs.readdirSync('./routes').forEach(function (file) {
    // Avoid to read this current file.
    if (file === path.basename(__filename)) { return; }

    // Load the route file.
    require('./' + file)(app);
  });
};

Com esse módulo, é realmente fácil criar uma nova definição e implementação de rota. Por exemplo hello.js:

function hello(req, res) {
  res.send('Hello world');
}

module.exports = function (app) {
  app.get('/api/hello_world', hello);
};

Cada módulo de rota é autônomo .


Você usa um gerador para criar essa estrutura?
Ashish


17

Eu acho que é uma ótima maneira de fazer isso. Não estou limitado a expressar, mas já vi vários projetos node.js no github fazendo a mesma coisa. Eles retiram os parâmetros de configuração + módulos menores (em alguns casos, todos os URI) são fatorados em arquivos separados.

Eu recomendaria passar por projetos expressos específicos no github para ter uma idéia. IMO do jeito que você está fazendo está correto.


16

agora é final de 2015 e depois de desenvolver minha estrutura por 3 anos e em projetos pequenos e grandes. Conclusão?

Não faça um MVC grande, mas separe-o em módulos

Assim...

Por quê?

  • Normalmente, trabalha-se em um módulo (por exemplo, produtos), que você pode alterar independentemente.

  • Você pode reutilizar módulos

  • Você é capaz de testá-lo separadamente

  • Você pode substituí-lo separadamente

  • Eles têm interfaces claras (estáveis)

    -No máximo, se houver vários desenvolvedores trabalhando, a separação de módulos ajuda

O projeto nodebootstrap tem uma abordagem semelhante à minha estrutura final. ( github )

Como é essa estrutura?

  1. Módulos pequenos e capsulados , cada um com MVC separado

  2. Cada módulo possui um package.json

  3. Testando como parte da estrutura (em cada módulo)

  4. Configuração global , bibliotecas e serviços

  5. Docker integrado, cluster, para sempre

Folderoverview (consulte a pasta lib para obter os módulos):

estrutura nodebootstrap


3
Seria útil se você pudesse atualizar a imagem da visão geral da pasta com os módulos individuais expandidos também, como um exemplo de como você os estruturaria também.
youngrrrr

8

Estou dando a estrutura de pastas no estilo MVC, por favor, veja abaixo.

Usamos a estrutura de pastas abaixo para nossos aplicativos Web grandes e médios.

 myapp   
|
|
|____app
|      |____controllers
|      |    |____home.js
|      |
|      |____models
|      |     |___home.js
|      |
|      |____views
|           |___404.ejs
|           |___error.ejs
|           |___index.ejs
|           |___login.ejs
|           |___signup.ejs
|   
|
|_____config
|     |___auth.js
|     |___constants.js
|     |___database.js
|     |___passport.js
|     |___routes.js
|
|
|____lib
|    |___email.js
|
|____node_modules
|
|
|____public.js
|    |____css
|    |    |__style.css
|    |    
|    |____js
|    |    |__script.js
|    |
|    |____img
|    |    |__img.jpg
|    |
|    |
|    |____uploads
|         |__img.jpg
|      
|   
|
|_____app.js
|
|
|
|_____package.json

Eu criei um módulo npm para geração de estruturador de pastas mvc express.

Encontre o seguinte https://www.npmjs.com/package/express-mvc-generator

Apenas etapas simples para gerar e usar esses módulos.

i) instale o módulo npm install express-mvc-generator -g

ii) verificar opções express -h

iii) Gere estrutura expressa de mvc express myapp

iv) Instalar dependências npm install::

v) Abra o seu config / database.js. Por favor, configure o seu mongo db.

vi) Execute o aplicativo node appounodemon app

vii) Verifique o URL http: // localhost: 8042 / signup OU http: // yourip: 8042 / signup


7

Já faz um bom tempo desde a última resposta a esta pergunta e o Express também lançou recentemente a versão 4, que adicionou algumas coisas úteis para organizar a estrutura do seu aplicativo.

Abaixo, há uma postagem de blog longa e atualizada sobre as práticas recomendadas sobre como estruturar seu aplicativo Express. http://www.terlici.com/2014/08/25/best-practices-express-structure.html

Há também um repositório do GitHub que aplica os conselhos deste artigo. Está sempre atualizado com a versão mais recente do Express.
https://github.com/terlici/base-express


7

Eu não acho que seja uma boa abordagem adicionar rotas à configuração. Uma estrutura melhor poderia ser algo como isto:

application/
| - app.js
| - config.js
| - public/ (assets - js, css, images)
| - views/ (all your views files)
| - libraries/ (you can also call it modules/ or routes/)
    | - users.js
    | - products.js
    | - etc...

Portanto, products.js e users.js conterão todas as suas rotas, todas dentro da lógica.


6

Bem, eu coloquei minhas rotas como um arquivo json, que li no início, e em um loop for no app.js, configurei as rotas. O route.json inclui qual visualização deve ser chamada e a chave para os valores que serão enviados para a rota.
Isso funciona para muitos casos simples, mas tive que criar manualmente algumas rotas para casos especiais.


6

Eu escrevi um post exatamente sobre esse assunto. Basicamente, utiliza um routeRegistrarque itera através de arquivos na pasta /controllerschamando sua função init. A função initusa a appvariável express como parâmetro, para que você possa registrar suas rotas da maneira que desejar.

var fs = require("fs");
var express = require("express");
var app = express();

var controllersFolderPath = __dirname + "/controllers/";
fs.readdirSync(controllersFolderPath).forEach(function(controllerName){
    if(controllerName.indexOf("Controller.js") !== -1){
        var controller = require(controllersFolderPath + controllerName);
        controller.init(app);
    }
});

app.listen(3000);


4

1) O sistema de arquivos do seu projeto Express pode ser como:

/ ...
/lib
/node_modules
/public
/views
      app.js
      config.json
      package.json

app.js - seu contêiner global de aplicativos

2) Arquivo principal do módulo (lib / mymodule / index.js):

var express = require('express');    
var app = module.exports = express();
// and load module dependencies ...  

// this place to set module settings
app.set('view engine', 'jade');
app.set('views', __dirname + '/views');

// then do module staff    
app.get('/mymodule/route/',function(req,res){ res.send('module works!') });

3) Conecte o módulo no app.js principal

...
var mymodule = require('mymodule');
app.use(mymodule);

4) Exemplo de lógica

lib/login
lib/db
lib/config
lib/users
lib/verify
lib/
   /api/ 
   ...
lib/
   /admin/
      /users/
      /settings/
      /groups/
...
  • Melhor para teste
  • Melhor para escala
  • Separar depende do módulo
  • Agrupando rota por funcionalidade (ou módulos)

tj diz / mostra no Vimeo uma idéia interessante de como modularizar o aplicativo expresso - aplicativos Web modulares com Node.js. e Express . Poderoso e simples.


4

http://locomotivejs.org/ fornece uma maneira de estruturar um aplicativo criado com Node.js e Express.

A partir do site:

"A Locomotive é uma estrutura da Web para o Node.js. A Locomotive suporta padrões MVC, rotas RESTful e convenções sobre configuração, enquanto se integra perfeitamente a qualquer banco de dados e mecanismo de modelos. A Locomotive se baseia no Express, preservando o poder e a simplicidade que você espera do nó ".


3

Recentemente, abracei os módulos como mini-aplicativos independentes.

|-- src
  |--module1
  |--module2
     |--www
       |--img
       |--js
       |--css
     |--#.js
     |--index.ejs
  |--module3
  |--www
     |--bower_components
     |--img
     |--js
     |--css
  |--#.js
  |--header.ejs
  |--index.ejs
  |--footer.ejs

Agora, para qualquer roteamento de módulo (# .js), visualizações (* .ejs), js, css e ativos estão próximos um do outro. o roteamento do submódulo é configurado no pai # .js com duas linhas adicionais

router.use('/module2', opt_middleware_check, require('./module2/#'));
router.use(express.static(path.join(__dirname, 'www')));

Dessa forma, até os subsub-módulos são possíveis.

Não se esqueça de definir a exibição no diretório src

app.set('views', path.join(__dirname, 'src'));

qualquer link para github com tal estrutura interessado em ver como rotas, pontos de vista e modelos estão sendo carregados
Muhammad Umer

Eu acho que tudo está explicado. As rotas são apenas rotas expressas clássicas. As visualizações precisam ser carregadas com o prefixo dos nomes dos módulos; os modelos precisam ser carregados referenciando o caminho relativo.
zevero

Na minha última linha, defino a exibição no diretório src. Portanto, a partir daqui, todas as visualizações são acessíveis em relação ao diretório src. Nada chique.
zevero

1

É assim que parece a maior parte da minha estrutura de diretórios de projeto expresso.

Eu costumo fazer uma express dirnameinicialização do projeto, perdoar minha preguiça, mas é muito flexível e extensível. PS - você precisa express-generatordisso (para quem está procurando sudo npm install -g express-generator, sudo porque você está instalando globalmente)

|-- bin
    |-- www //what we start with "forever"
|-- bower_components
|-- models
    |-- database.js
    |-- model1.js //not this exact name ofcourse.
    |-- .
|-- node_modules
|-- public
    |-- images
    |-- javascripts
        |-- controllers
        |-- directives
        |-- services
        |-- app.js
        |-- init.js //contains config and used for initializing everything, I work with angular a lot.
    |-- stylesheets
|-- routes
    |-- some
    |-- hierarchy
    .
    .
|-- views
    |-- partials
    |-- content
|-- .env
|-- .env.template
|-- app.js
|-- README.md

Você deve estar se perguntando por que arquivos .env? Porque eles trabalham! Eu uso o dotenvmódulo em meus projetos (muito recentemente) e funciona! Entre nessas duas declarações em app.jsouwww

var dotenv = require('dotenv');
dotenv.config({path: path.join(__dirname + "/.env")});

E outra linha para configurar rapidamente /bower_componentspara veicular conteúdo estático sob o recurso/ext

app.use('/ext', express.static(path.join(__dirname, 'bower_components')));

Provavelmente pode ser adequado para pessoas que desejam usar o Express e o Angular juntos, ou apenas se expressam sem essa javascriptshierarquia, é claro.


1

Minha estrutura expressa 4. https://github.com/odirleiborgert/borgert-express-boilerplate

Pacotes

View engine: twig
Security: helmet
Flash: express-flash
Session: express-session
Encrypt: bcryptjs
Modules: express-load
Database: MongoDB
    ORM: Mongoose
    Mongoose Paginate
    Mongoose Validator
Logs: winston + winston-daily-rotate-file
Nodemon
CSS: stylus
Eslint + Husky

Estrutura

|-- app
    |-- controllers
    |-- helpers
    |-- middlewares
    |-- models
    |-- routes
    |-- services
|-- bin
|-- logs
|-- node_modules
|-- public
    |-- components
    |-- images
    |-- javascripts
    |-- stylesheets
|-- views
|-- .env
|-- .env-example
|-- app.js
|-- README.md

0

Uma maneira simples de estruturar seu aplicativo expresso:

  • No principal index.js, a seguinte ordem deve ser mantida.

    todos app.set deve ser o primeiro.

    todo app.use deve ser o segundo.

    seguido por outras APIs com suas funções ou rota-continue em outros arquivos

    Exapmle

    app.use ("/ password", passwordApi);

    app.use ("/ user", userApi);

    app.post ("/ token", passport.createToken);

    app.post ("/ logout", passport.logout)


0

Melhor maneira de estrutura MVC para o projeto ExpressJs com guidão e Passportjs

- app
      -config 
        -passport-setup.js
      -controllers
      -middleware
      -models
      -routes
      -service
    -bin
      -www
      -configuration.js
      -passport.js
    -node_modules
    -views
     -handlebars page
    -env
    -.gitignore
    -package.json
    -package-lock.json

@ sandro-munda, por favor verifique
Manishkumar Bhavnani
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.