Estrutura de diretórios e arquivos
Um dos primeiros desafios quando começamos uma aplicação em Node.js é estruturar o projeto. Uma grande conveniência do Node, por ser javascript, é a liberdade para estrutura, design de código, patterns e etc. Porém, isso também pode gerar confusão para os novos desenvolvedores.
A maioria dos projetos no github, por exemplo, possuem estruturas que diferem entre si, essa variação acontece pois cada desenvolvedor cria a estrutura da forma que se enquadrar melhor a sua necessidade.
Mesmo assim podemos aproveitar os padrões comuns entre esses projetos para estruturar nossa aplicação de maneira que atenda as nossas necessidades e também fique extensível, legível e facilmente integrável com ferramentas externas, como Travis, CodeClimate e etc.
O diretório raiz
O diretório raiz do projeto é o ponto de entrada e fornece a primeira impressão. No exemplo a seguir, temos uma estrutura comum em aplicações usando o framework Express.js:
1 ├── app.js
2 ├── controllers
3 ├── middlewares
4 ├── models
5 ├── package.json
6 └── tests
Essa estrutura é legível e organizada, mas com o crescimento da aplicação pode misturar diretórios de código com diretórios de teste, build e etc. Um padrão comum em diversas linguagens é armazenar o código da aplicação em um diretório source normalmente chamado src.
1 ├── package.json
2 ├── src
3 │ ├── controllers
4 │ ├── middlewares
5 │ └── models
6 │ └── app.js
7 │ └── server.js
8 └── tests
Dessa maneira o código da aplicação é isolado em um diretório deixando a raiz do projeto mais limpa e acabando com a mistura de diretórios de código com diretórios de testes e arquivos de configuração.
O que fica no diretório raiz?
No exemplo acima movemos o código da aplicação para o diretório src mas mantivemos o diretório tests, isso acontece porque testes são executados por linha de comando ou por outras ferramentas. Inclusive os test runners como mocha e karma esperam que o diretório tests esteja no diretório principal. Outros diretórios comumente localizados na raiz são scripts de suporte ou build, exemplos, documentação e arquivos estáticos. No exemplo abaixo vamos incrementar nossa aplicação com mais alguns diretórios:
1 ├── env
2 │ ├── dev.env
3 │ └── prod.env
4 ├── package.json
5 ├── public
6 │ ├── assets
7 │ ├── css
8 │ ├── images
9 │ └── js
10 ├── scripts
11 │ └── deploy.sh
12 ├── src
13 │ ├── server.js
14 │ ├── app.js
15 │ ├── controllers
16 │ ├── middlewares
17 │ ├── models
18 │ └── routes
19 └── tests
O diretório public é responsável por guardar tudo aquilo que vai ser entregue para o usuário. Mantê-lo na raiz facilita a criação de rotas de acesso e a movimentação dos assets, caso necessário. Os diretórios scripts e env são relacionados a execução da aplicação e serão chamados por alguma linha de comando ou ferramenta externa, colocá-los em um diretório acessível promove a usabilidade.
Dentro do diretório source
Agora que já entendemos o que fica fora do diretório src vamos ver como organizá-lo baseado nas nossas necessidades.
1 ├── src
2 │ ├── app.js
3 │ ├── server.js
4 │ ├── controllers
5 │ ├── middlewares
6 │ ├── models
7 │ └── routes
Essa estrutura é bastante utilizada, ela é clara e separa as responsabilidades de cada componente, além de permitir o carregamento dinâmico.
Responsabilidades diferentes dentro de um mesmo source
Ás vezes, quando começamos uma aplicação, já sabemos o que será desacoplado e queremos dirigir nosso design para que no futuro seja possível separar e tornar parte do código um novo módulo. Outra necessidade comum é ter APIs específicas para diferentes tipos de clientes, como no exemplo a seguir:
1 └── src
2 ├── app.js
3 ├── server.js
4 ├── mobile
5 │ ├── controllers
6 │ ├── index.js
7 │ ├── middlewares
8 │ ├── models
9 │ └── routes
10 └── web
11 ├── controllers
12 ├── index.js
13 ├── middlewares
14 ├── models
15 └── routes
Esse cenário funciona bem mas pode dificultar o reúso de código entre os componentes. Então, antes de implementar, tenha certeza que seu caso de uso permite a separação dos clientes sem que um dependa do outro.
Server e Client no mesmo repositório
Muitas vezes temos o backend e o front-end separados mas versionados juntos, no mesmo repositório, seja ele git, mercurial, ou qualquer outro controlador de versão. A estrutura mais comum que pude observar na comunidade para esse tipo de situação é separar o server e o client como no exemplo abaixo:
1 ├── client
2 │ ├── controllers
3 │ ├── models
4 │ └── views
5 ├── client.js
6 ├── config
7 ├── package.json
8 ├── server
9 │ ├── controllers
10 │ ├── models
11 │ └── routes
12 └── tests
Essa estrutura é totalmente adaptável ás necessidades. No exemplo acima, os testes de ambas as aplicações estão no diretório tests no diretório raiz. Assim, se o projeto for adicionado em uma integração contínua ele vai executar a bateria de testes de ambas as aplicações. O server.js e o client.js são responsáveis por iniciar as respectivas aplicações. Podemos ter um npm start no package.json que inicie os dois arquivos juntos.
Separação por funcionalidade
Um padrão bem frequente é o que promove a separação por funcionalidade. Nele abstraímos os diretórios baseado nas funcionalidades e não nas responsabilidades, como no exemplo abaixo:
1 └── src
2 ├── app.js
3 ├── server.js
4 ├── orders
5 │ ├── orders.controller.js
6 │ └── orders.routes.js
7 └── products
8 ├── products.controller.js
9 ├── products.model.js
10 └── products.routes.js
Essa estrutura possui uma boa legibilidade e escalabilidade, por outro lado, pode crescer muito tornando o reúso de componentes limitado e dificultando o carregamento dinâmico de arquivos.
Conversão de nomes
Quando separamos os diretórios por suas responsabilidades pode não ser necessário deixar explícito a responsabilidade no nome do arquivo.
Veja o exemplo abaixo:
1 └── src
2 ├── controllers
3 │ └── products.js
4 └── routes
5 └── products.js
Como o nosso diretório é responsável por informar qual a responsabilidade dos arquivos que estão dentro dele, podemos nomear os arquivos sem adicionar o sufixo _ + nome do diretório (por exemplo: “_controller”). Além disso, o javascript permite nomear um módulo quando o importamos, permitindo que mesmo arquivos com o mesmo nome sejam facilmente distinguidos por quem está lendo o código, veja o exemplo:
1 Import ProductsController from './src/controllers/products';
2 Import ProductsRoute from './src/routes/products';
Dessa maneira não adicionamos nenhuma informação desnecessária ao nomes dos arquivos e ainda mantemos a legibilidade do código.
No decorrer do livro utilizaremos o exemplo seguindo o padrão MVC com a diretório source e os demais diretórios dentro, como controllers, models e etc.