Gallalaus: An Expedition into Full Stack Development - Project Structure

Embark on the next leg of our Full Stack Development expedition with this third post in the Gallalaus series! In this stop, I'll share how I prefer to structure my projects for a smoother journey ahead.

Main project structure

This project will be structured as a monorepo. Initially, I won't be using any specialized monorepo tools aside from pnpm workspace. This choice will help in organizing packages and running commands efficiently. By this, I mean that the entire codebase will reside within a single Git repository.

   | ── .gitlab 
   |  | ── merge_request_templates 
   |   |   |── merge_request_templates.md
   | ── backend 
   | ── data 
   | ── frontend 
   | ── logs 
   | ── test 
   | ── README.md 
   | ── package.json 
   | ── pnpm-workspace.yaml 
   | ── pnpm-lock.yaml 
   | ── CHANGELOG.md 
   | ── .gitignore 
   | ── .env.example 
   | ── .gitlab-ci.yml 
   | ── backend-build-start-script.sh 
   | ── docker-compose.yml

Let's break this down quickly:

  • .gitlab folder houses GitLab configurations, including a customized merge request/pull request description template.

  • the backend folder holds the monolithic NestJS backend code for now.

  • the data folder stores MongoDB Docker data.

  • the frontend folder hosts the Angular app.

  • the logs folder stores application logs.

  • the test folder includes API tests.

  • backend-build-start-script is a Bash script used to build the backend Docker image and launch Docker Compose for the project.

Nestjs proejct structure

│── Dockerfile
│── nest-cli.json
│── package.json
│── pnpm-lock.yaml
│── README.md
│── src
│   │── app.module.ts
│   │── config
│   │── debug
│   │   │── debug.controller.spec.ts
│   │   │── debug.controller.ts
│   │── definitions
│   │── health
│   │   │── health.controller.spec.ts
│   │   │── health.controller.ts
│   │── main.ts
│   │── shared
│   │── tasks
│── test
│   │── app.e2e-spec.ts
│   │── jest-e2e.json
│── tsconfig.build.json
│── tsconfig.json

Let us look closely now at each layer and understand what it contains:

config

  │   │── config
  │   │   │──config.module.ts
  │   │   │── config.service.spec.ts
  │   │   │── config.service.ts
  │   │   │── env.schema.ts
  │   │   │── logger
  │   │   │   │──index.ts
  │   │   │   │── logger.config.ts

The config folder stores application and third-party library configurations. I prefer the approach of exporting from an index file, as it keeps things compact and easy to maintain, as seen in the logger folder and throughout this project. This folder follows the classic NestJS module structure, as I appreciate the benefits of this framework. An important aspect to highlight here is the env.schema file, which serves as a schema mapping for the application's required environment variables. This approach ensures that we always have clarity about our required variables and allows us to validate their availability during the application's bootstrap.

definitions

│   │── definitions
│   │   │── enums
│   │   │   │── index.ts
│   │   │── index.ts
│   │   │── interfaces
│   │   │   │── index.ts
│   │   │   │── tasks.interfaces.ts
│   │   │   │── tasks.repository.interfaces.ts
│   │   │── types
│   │       │── index.ts
│   │       │── tasks.repository.types.ts

The definitions folder is one of the straightforward ones, as it should contain the entire definitions that will be shared between the modules, as we can see these are: types, interfaces, enums, etc. I will not go into much detail here.

shared

│   │── shared
│   │   │── middleware
│   │   │   │── db-logger.middleware.ts
│   │   │   │── logger.middleware.ts
│   │   │   │── trace.middleware.ts
│   │   │── request-context
│   │   │   │── request-context.module.ts
│   │   │   │── request-context.service.spec.ts
│   │   │   │── request-context.service.ts
│   │   │── errors
│   │   │── http
│   │   │   │── exceptions
│   │   │   │── filters
│   │   │   │   │── execption-response.filter.ts
│   │   │── validation-pipes
│   │   │  │── index.ts
│   │   │  │── object-id-validation.pipe.ts
│   │   │── utils
│   │   │   │── utils.module.ts
│   │   │   │── utils.service.spec.ts
│   │   │   │── utils.service.ts

The shared folder, contains, as we can see, all the shared code, that will be used by our business modules. Some important notes here are:

  • utils module, should contain utils methods, that are useful in the entire app, like: password hash method, transformation methods, and so on.
  • request-context module is a module that will allow us to make use of some specific global variables through the application. As an example, we can see the tracing-id of each request.
  • the http/filters will contain useful exception filters that can be applied on top of the http calls. see this exception filters nestjs

tasks-module

│   │── tasks
│   │   │── dto
│   │   │   │── create-task.dto.ts
│   │   │   │── find-many-query.dto.ts
│   │   │   │── index.ts
│   │   │   │── update-task.dto.ts
│   │   │── models
│   │   │   │── task.model.ts
│   │   │   │── index.ts
│   │   │── repository
│   │   │   │── tasks.repository.ts
│   │   │   │── index.ts
│   │   │── tasks.controller.spec.ts
│   │   │── tasks.controller.ts
│   │   │── tasks.module.ts
│   │   │── tasks.service.spec.ts
│   │   │── tasks.service.ts

This module follows the standard Nest module structure. A few important points to note:

  • in the dtos folder, DTOs should implement an interface that defines their structure for sharing across the application.
  • the repository is an abstract class responsible for accessing the data source. I'll delve deeper into this topic in a dedicated article. This example demonstrates how the application modules will be structured

Angular project structure

For the Angular part, we'll adopt a component-based style. Here's the initial proposed structure:

  ├── angular.json
├── karma.conf.js
├── package.json
├── pnpm-lock.yaml
├── README.md
├── src
│   ├── app
│   │   ├── app.component.html
│   │   ├── app.component.scss
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   ├── app.module.ts
│   │   ├── app-routing.module.ts
│   │   ├── components
│   │   │   ├── board
│   │   │   │   ├── board.component.html
│   │   │   │   ├── board.component.scss
│   │   │   │   ├── board.component.spec.ts
│   │   │   │   └── board.component.ts
│   │   │   ├── definitions
│   │   │   │   ├── enums
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── priority.enum.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── interfaces
│   │   │   │       ├── index.ts
│   │   │   │       ├── task.interface.ts
│   │   │   │       └── task-list.interface.ts
│   │   │   ├── navigation
│   │   │   │   ├── header
│   │   │   │   │   ├── header.component.html
│   │   │   │   │   ├── header.component.scss
│   │   │   │   │   ├── header.component.spec.ts
│   │   │   │   │   └── header.component.ts
│   │   │   │   └── navigation.module.ts
│   │   │   ├── task
│   │   │   │   ├── task.component.html
│   │   │   │   ├── task.component.scss
│   │   │   │   ├── task.component.spec.ts
│   │   │   │   └── task.component.ts
│   │   │   ├── task-add
│   │   │   │   ├── task-add.component.html
│   │   │   │   ├── task-add.component.scss
│   │   │   │   ├── task-add.component.spec.ts
│   │   │   │   └── task-add.component.ts
│   │   │   ├── task-list
│   │   │   │   ├── task-list.component.html
│   │   │   │   ├── task-list.component.scss
│   │   │   │   ├── task-list.component.spec.ts
│   │   │   │   └── task-list.component.ts
│   │   │   └── task-list-add
│   │   │       ├── task-list-add.component.html
│   │   │       ├── task-list-add.component.scss
│   │   │       ├── task-list-add.component.spec.ts
│   │   │       └── task-list-add.component.ts
│   │   └── material.module.ts
│   ├── assets
│   ├── environments
│   │   ├── environment.prod.ts
│   │   └── environment.ts
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   ├── polyfills.ts
│   ├── styles.scss
│   └── test.ts
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json

For the frontend Angular application, it's a standard Angular app for now. We'll keep the current structure as we explore our development direction and goals.

In the next part of our journey, we'll focus on system design, identifying necessary components, establishing communication between the frontend and backend, designing the database, and working toward achieving the MVP status quo. I'll also aim to provide simple REST API definitions using the OpenAPI specification.

As we conclude this stage of our journey, we're one step closer to mastering Full Stack Development. Stay tuned for more insights and discoveries in the next chapter.