Hola a todos, espero que estén bien mientras leen este documento. Esta es la última versión (v2.0.0) del fullstack template de Avila Tek, en esta ocasión hemos construido una versión que se adapta mejor a nuestra forma de trabajo. Esperamos que facilite tareas y que deje las bases de nuestra arquitectura tecnológica.
Con cariño, José
This might sound obvious, but you need to have node
and npm
installed. We recommend using nvm for the installation. If so, you just need to run the following command:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
Then restart (close and reopen) the terminal, and then run nvm install --lts
This is important because it will install the Long Term Support version of Node, which is currently v22.10.0
This repo is a turborepo itself. But it isn't like the turbo repo you will use for coding. What we have here is a command line interface (CLI) that acts like a little helper for you to clone the actual structure of the project that you will be using.
So in this repo you will have the following structure:
.
├── CHANGELOG.md
├── README.md
├── examples
│ ├── README.md
│ ├── with-graphql-mongoose
│ │ ├── README.md
│ │ ├── apps
│ │ │ ├── admin
│ │ │ │ ├── Dockerfile
│ │ │ │ ├── README.md
│ │ │ │ ├── jest.config.ts
│ │ │ │ ├── jest.setup.ts
│ │ │ │ ├── next.config.mjs
│ │ │ │ ├── package.json
│ │ │ │ ├── postcss.config.mjs
│ │ │ │ ├── public
│ │ │ │ ├── sentry.client.config.ts
│ │ │ │ ├── sentry.edge.config.ts
│ │ │ │ ├── sentry.server.config.ts
│ │ │ │ ├── src
│ │ │ │ ├── tailwind.config.ts
│ │ │ │ └── tsconfig.json
│ │ │ ├── api
│ │ │ │ ├── Dockerfile
│ │ │ │ ├── package.json
│ │ │ │ ├── src
│ │ │ │ ├── tsconfig.json
│ │ │ │ └── tsup.config.ts
│ │ │ └── client
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ ├── jest.config.ts
│ │ │ ├── jest.setup.ts
│ │ │ ├── next.config.mjs
│ │ │ ├── package.json
│ │ │ ├── postcss.config.mjs
│ │ │ ├── public
│ │ │ ├── sentry.client.config.ts
│ │ │ ├── sentry.edge.config.ts
│ │ │ ├── sentry.server.config.ts
│ │ │ ├── src
│ │ │ ├── tailwind.config.ts
│ │ │ └── tsconfig.json
│ │ ├── biome.json
│ │ ├── docker-compose.yml
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── packages
│ │ │ ├── typescript-config
│ │ │ │ ├── api.json
│ │ │ │ ├── base.json
│ │ │ │ ├── nextjs.json
│ │ │ │ ├── package.json
│ │ │ │ └── react-library.json
│ │ │ └── ui
│ │ │ ├── package.json
│ │ │ ├── src
│ │ │ ├── tsconfig.json
│ │ │ └── turbo
│ │ └── turbo.json
│ ├── with-graphql-prisma
│ │ ├── README.md
│ │ ├── apps
│ │ │ ├── admin
│ │ │ │ ├── Dockerfile
│ │ │ │ ├── README.md
│ │ │ │ ├── jest.config.ts
│ │ │ │ ├── jest.setup.ts
│ │ │ │ ├── next.config.mjs
│ │ │ │ ├── package.json
│ │ │ │ ├── postcss.config.mjs
│ │ │ │ ├── public
│ │ │ │ ├── sentry.client.config.ts
│ │ │ │ ├── sentry.edge.config.ts
│ │ │ │ ├── sentry.server.config.ts
│ │ │ │ ├── src
│ │ │ │ ├── tailwind.config.ts
│ │ │ │ └── tsconfig.json
│ │ │ ├── api
│ │ │ │ ├── Dockerfile
│ │ │ │ ├── package.json
│ │ │ │ ├── prisma
│ │ │ │ ├── src
│ │ │ │ ├── tsconfig.json
│ │ │ │ └── tsup.config.ts
│ │ │ └── client
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ ├── jest.config.ts
│ │ │ ├── jest.setup.ts
│ │ │ ├── next.config.mjs
│ │ │ ├── package.json
│ │ │ ├── postcss.config.mjs
│ │ │ ├── public
│ │ │ ├── sentry.client.config.ts
│ │ │ ├── sentry.edge.config.ts
│ │ │ ├── sentry.server.config.ts
│ │ │ ├── src
│ │ │ ├── tailwind.config.ts
│ │ │ └── tsconfig.json
│ │ ├── biome.json
│ │ ├── docker-compose.yml
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── packages
│ │ │ ├── typescript-config
│ │ │ │ ├── api.json
│ │ │ │ ├── base.json
│ │ │ │ ├── nextjs.json
│ │ │ │ ├── package.json
│ │ │ │ └── react-library.json
│ │ │ └── ui
│ │ │ ├── package.json
│ │ │ ├── src
│ │ │ ├── tsconfig.json
│ │ │ └── turbo
│ │ └── turbo.json
│ ├── with-restful-mongoose
│ │ ├── README.md
│ │ ├── apps
│ │ │ ├── admin
│ │ │ │ ├── Dockerfile
│ │ │ │ ├── README.md
│ │ │ │ ├── jest.config.ts
│ │ │ │ ├── jest.polyfills.ts
│ │ │ │ ├── jest.setup.ts
│ │ │ │ ├── next.config.mjs
│ │ │ │ ├── package.json
│ │ │ │ ├── postcss.config.mjs
│ │ │ │ ├── public
│ │ │ │ ├── src
│ │ │ │ ├── tailwind.config.ts
│ │ │ │ └── tsconfig.json
│ │ │ ├── api
│ │ │ │ ├── Dockerfile
│ │ │ │ ├── package.json
│ │ │ │ ├── src
│ │ │ │ ├── tsconfig.json
│ │ │ │ └── tsup.config.ts
│ │ │ └── client
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ ├── next.config.mjs
│ │ │ ├── package.json
│ │ │ ├── postcss.config.mjs
│ │ │ ├── public
│ │ │ ├── src
│ │ │ ├── tailwind.config.ts
│ │ │ └── tsconfig.json
│ │ ├── biome.json
│ │ ├── docker-compose.yml
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── packages
│ │ │ ├── typescript-config
│ │ │ │ ├── api.json
│ │ │ │ ├── base.json
│ │ │ │ ├── nextjs.json
│ │ │ │ ├── package.json
│ │ │ │ └── react-library.json
│ │ │ └── ui
│ │ │ ├── package.json
│ │ │ ├── src
│ │ │ ├── tsconfig.json
│ │ │ └── turbo
│ │ └── turbo.json
│ └── with-restful-prisma
│ ├── README.md
│ ├── apps
│ │ ├── admin
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ ├── next.config.mjs
│ │ │ ├── package.json
│ │ │ ├── postcss.config.mjs
│ │ │ ├── public
│ │ │ ├── src
│ │ │ ├── tailwind.config.ts
│ │ │ └── tsconfig.json
│ │ ├── api
│ │ │ ├── Dockerfile
│ │ │ ├── package.json
│ │ │ ├── prisma
│ │ │ ├── src
│ │ │ ├── tsconfig.json
│ │ │ └── tsup.config.ts
│ │ └── client
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── next.config.mjs
│ │ ├── package.json
│ │ ├── postcss.config.mjs
│ │ ├── public
│ │ ├── src
│ │ ├── tailwind.config.ts
│ │ └── tsconfig.json
│ ├── biome.json
│ ├── docker-compose.yml
│ ├── package-lock.json
│ ├── package.json
│ ├── packages
│ │ ├── typescript-config
│ │ │ ├── api.json
│ │ │ ├── base.json
│ │ │ ├── nextjs.json
│ │ │ ├── package.json
│ │ │ └── react-library.json
│ │ └── ui
│ │ ├── package.json
│ │ ├── src
│ │ ├── tsconfig.json
│ │ └── turbo
│ └── turbo.json
└── packages
└── create-avilatek
└── README.md
You might notice that we have a folder called examples
that contains (as of today, 4) project structures, all named with-[restful/graphql]-[mongoose/prisma]
. The CLI should clone any of the project structures based on the parameters that were configured.
But let's take a deep look at the projects themselves.
Let's take, for example, the with-restful-prisma
example. The structure is the following:
.
├── README.md
├── apps
│ ├── admin
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── next.config.mjs
│ │ ├── package.json
│ │ ├── postcss.config.mjs
│ │ ├── public
│ │ │ ├── file-text.svg
│ │ │ ├── globe.svg
│ │ │ ├── next.svg
│ │ │ ├── vercel.svg
│ │ │ └── window.svg
│ │ ├── src
│ │ │ ├── app
│ │ │ │ ├── favicon.ico
│ │ │ │ ├── fonts
│ │ │ │ ├── globals.css
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── page.module.css
│ │ │ │ └── page.tsx
│ │ │ └── context
│ │ │ └── react-query.tsx
│ │ ├── tailwind.config.ts
│ │ └── tsconfig.json
│ ├── api
│ │ ├── Dockerfile
│ │ ├── package.json
│ │ ├── prisma
│ │ │ ├── dev.db
│ │ │ ├── dev.db-journal
│ │ │ ├── migrations
│ │ │ │ ├── 20240819155219_init
│ │ │ │ └── migration_lock.toml
│ │ │ └── schema.prisma
│ │ ├── src
│ │ │ ├── app.ts
│ │ │ ├── index.ts
│ │ │ ├── instrument.ts
│ │ │ ├── plugins
│ │ │ │ └── prisma.ts
│ │ │ └── server.ts
│ │ ├── tsconfig.json
│ │ └── tsup.config.ts
│ └── client
│ ├── Dockerfile
│ ├── README.md
│ ├── next.config.mjs
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── public
│ │ ├── file-text.svg
│ │ ├── globe.svg
│ │ ├── next.svg
│ │ ├── vercel.svg
│ │ └── window.svg
│ ├── src
│ │ ├── app
│ │ │ ├── favicon.ico
│ │ │ ├── fonts
│ │ │ ├── globals.css
│ │ │ ├── layout.tsx
│ │ │ ├── page.module.css
│ │ │ └── page.tsx
│ │ └── context
│ │ └── react-query.tsx
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── biome.json
├── docker-compose.yml
├── package-lock.json
├── package.json
├── packages
│ ├── typescript-config
│ │ ├── api.json
│ │ ├── base.json
│ │ ├── nextjs.json
│ │ ├── package.json
│ │ └── react-library.json
│ └── ui
│ ├── package.json
│ ├── src
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ └── code.tsx
│ ├── tsconfig.json
│ └── turbo
│ └── generators
│ ├── config.ts
│ └── templates
└── turbo.json
You are seeing a typical turborepo example. We have made some customizations to help you out with the initial setup. That is why you have 3 apps:
- admin
- api
- client
The admin and client apps are Next.js apps currently running version 14 and configured to use the appRouter
(the React Server Components router).
The api app is a Fastify v4 server. Based on the selected communication protocol, it can be run as a GraphQL or a REST API. Also, depending on the selected database, it could be using Mongoose or Prisma.
On the other hand, the packages
folder contains the shared logic or UI between multiple parts of the project. The process for determining what should be in packages and what doesn't belong there is pretty simple:
- Is the UI component an atom?
- If so, put it in
@package/UI
- If not, ask if the component is used in both client and admin
- If so, put it in
@package/UI
- If not, put it in the client/admin UI folder
- If so, put it in
- If so, put it in
- Ask if schema/dto/models could be used in any of client, admin, or api?
- If so, put it in
@package/models
- If not, put it in the schemas/dto/models folder of client/admin/api
- If so, put it in
- Ask if a function can or will be reused in any of client, admin, or api?
- If so, put it in
@package/services
or@packages/utils
- If not, put it in the services/utils folder of client/admin/api
- If so, put it in
See the refactoring section for more on this topic.
With this, I believe you are more than ready to start coding, but there is an entire section dedicated to a deeper explanation of each app structure.
So you will be spending most of your time coding in VSCode, Zed, or any other code editor. As of today, we highly recommend using VSCode, but we might change our recommendation soon 👀.
You will be using GitHub and Lark for chatting and issue tracking.
💡 We have developmented our unique workflow for Git; the idea is pretty simple.
So every project in Avila Tek has a group of high-level features called epics. An epic should have multiple features and requirements called user stories (US or HU), and a US could have one or more tasks. All US or HU are grouped into two-week sprints. So when a sprint starts, every dev will have a workload assigned.
Ready? Here's how we make features in Avila Tek; it's pretty simple.
- We select a user story that doesn't have a blocker and go to GitHub to create an issue for it.
- Then we go to the local repository and create a branch using that same ID
- e.g.,
feat/estei-0001
,fix/clabe-0231
,chore/kaizen-3401
- e.g.,
- Then make a simple change (like creating a new file) and commit that change
- Use this message:
chore(admin/client/api): starting chore/kaizen-3401
(adjust accordingly)
- Use this message:
- Then immediately push the new branch to GitHub
- Start coding as you normally would, commit often, and try to make atomic changes within each commit
- Use conventional commits for your changes
- When you finish your code, go to GitHub and create a new pull request (PR) against the branch
- Depending on the strategy of the project, development or main
- Select a code reviewer (which is often the tech lead) and then create the PR
- Use a conventional commit message as the title of your PR
- Add a closing word to link the PR to the issue
- The reviewer will check your code and make comments if necessary, then will ask you for changes (again if necessary)
- If changes are needed, go back to that branch, make the changes, and push them back to the same
- If everything is OK, your changes will be merged to the branch, and the cycle will start over again
- The reviewer must close the PR and the issue, and delete the branch
- Please only keep active branches (stale or older than a month should be deleted)
We like simple and straightforward solutions. We believe that if we invest enough time in thinking through the problem in detail and simplifying our solution as much as possible, the code we produce is simple, reducing the potential for errors and increasing maintainability.
For this reason, it is important to us that all our engineers take the necessary time to think about problems and their solutions, share possible solutions with their colleagues, and design the most robust solution possible. Experience has taught us that the more we think about solutions and express their pros and cons, the better we are able to program an excellent solution expressed in simple and easy-to-maintain code.
To ensure a solution is robust, we like to consider some of the following things:
- All functionalities should be described through User Stories (US)
- All functionalities should be able to be activated or deactivated using feature flags
- All functionalities should have their respective migrations (and/or seeds)
- All functionalities should have at least two unit tests that validate the base case of their interface
- All refactors should be testable against the same set of tests as the original functionality
- All functionalities that involve integrations with third parties should be documented
- That is, all new third-party integrations that are made should be added to the relevant documentation
- As much as possible, all functionalities should measure the events that occur in the product
This Git branching strategy is designed to:
- Support scheduled releases, while we work on daily releases
- Align with three deployment environments.
- Facilitate tight integration with project management for issue tracking.
- Enhance collaboration and code quality via mandatory code reviews and CI/CD integration.
-
Main Branches:
main
: Reflects the production-ready state of the code. Only stable, tested code is merged here.staging
: Reflects the ready-to-production state of the code. Only stable, tested code is merged here.development
: Integrates all completed features for the next release. Serves as the base for the staging environment.
-
Supporting Branches:
- Feature Branches: Used by developers to work on new features or enhancements.
- Hotfix Branches: For urgent fixes that need to be applied to production.
-
Feature Branches (
<task-id>
):- Purpose: developers use these to work on individual features or tasks.
- Naming Convention: Include the Task ID.
- Example:
SOC-001
- Example:
- Creation: Branch off from
development
. - Integration: Merge back into
development
after code review and CI checks.
-
Hotfix Branches (
hotfix/<task-id>
):- Purpose: For critical fixes that need immediate deployment to production.
- Creation: Branch off from
main
. - Integration: Merge into both
main
anddevelopment
to ensure the fix is included in future releases. - Deployment: Upon merging into
main
, automatically deployed to production.
-
Feature development:
- Developer creates a feature branch from
development
. - Work is committed to the feature branch.
- Upon completion, a pull request is created to merge into
development
. - Team lead reviews the code.
- CI/CD runs automated tests.
- After approval and successful tests, merge into
development
.
- Developer creates a feature branch from
-
Release Preparation:
- At the end of the sprint (every 15 days), create a PR from
development
tostaging
. - Perform final testing and make any necessary fixes.
- Update documentation and version numbers.
- Tag the
staging
branch with the release version release candidate (v1.0.0-rc.0
).
- At the end of the sprint (every 15 days), create a PR from
-
Hotfixes:
- Create a hotfix branch from
main
. - Implement the fix.
- Create a pull request to merge into
main
,staging
anddevelopment
. - Team lead reviews the code.
- After approval and CI/CD checks, merge into both branches.
- Deploy to production upon merging into
main
.
- Create a hotfix branch from
-
Automated Testing:
- Triggered on pull request creation and updates.
- Runs unit tests and integration tests.
- Results must pass before merging.
-
Deployment:
- development Environment:
- Feature branches can be optionally deployed here for testing.
- Staging Environment:
staging
branch is automatically deployed upon updates.
- Production Environment:
main
branch is deployed after merging release branches or hotfixes.
- development Environment:
-
Pull Requests:
- Required for merging any branch.
- Must be reviewed and approved by the team lead.
- Review focuses on code quality, adherence to standards, and completeness.
-
Merge Strategy:
- Use merge commits to maintain a complete history.
- Avoid rebasing to keep the process simple for junior developers.
- Resolve conflicts locally, guided by the team lead.
-
Versioning:
- Adopt Semantic Versioning:
MAJOR.MINOR.PATCH
- MAJOR: Incompatible API changes.
- MINOR: Backward-compatible functionality.
- PATCH: Backward-compatible bug fixes.
- Adopt Semantic Versioning:
-
Tagging:
- Tag the
main
branch upon merging a release branch.- Example:
v1.2.0
- Example:
- Tags facilitate tracking of releases and rollback if necessary.
- Tag the