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.