Email delivery project containing the content of a newsletter, developed using Node.js and Typescript. The entire project adheres to the Clean Architecture designed by Robert C. Martin.
What are the advantages of building a project following this architecture?
The primary objective of Clean Architecture is the separation of concerns, achieved by dividing the software into distinct layers. Each layer includes at least one for business rules and another for interfaces. This approach results in a system that is:
- Independent of Frameworks
- Testable
- Independent of UI
- Independent of Database
- Independent of any external agency
- Entities Layer:
- User Entity
- Usecases Layer:
- Subscribe user on newsletter
- Send newsletter to subscribed users
- Unsubscribe user of newsletter
- In memory user repository
- Presenters Layer:
- Subscribe user controller
- Send newsletter controller
- Unsubscribe user controller
- Infra Layer:
- Email Service (nodemailer)
- Html Compiler Service (handlebars)
- Repository (mongodb)
- Main Layer:
- Express configuration
- Factory
- Middlewares
- Routes
To build the project image, a dockerfile with the multisage concept was used. Multistage builds make use of one Dockerfile with multiple FROM instructions. Each of these FROM instructions is a new build stage that can COPY artifacts from the previous stages.
There are two main reasons for why you’d want to use multi-stage builds:
- They allow you to run build steps in parallel, making your build pipeline faster and more efficient.
- They allow you to create a final image with a smaller footprint, containing only what's needed to run your program.
FROM node:18-bullseye-slim AS builder
Here we are using the official Node.js 18 image with slim version as the base image.
AS builder: Assigns a name to the build stage. This allows using multiple stages in a single Dockerfile.
WORKDIR /usr/src/app
Set the working directory inside the container.
COPY --chown=node:node package*.json ./
Copy package.json and package-lock.json to the working directory.
--chown=node:node: Sets ownership of the copied files to the specified user and group. This is a security best practice to avoid running processes as root.
COPY . .
Copy all files from the host to the container's working directory.
RUN npm run build
Runs the build script defined in the package.json. This likely compiles/transpiles the source code into a distributable form.
FROM node:18-bullseye-slim
Starts a new build stage using the same base image.
ENV NODE_ENV dev
Sets the environment variable NODE_ENV to 'dev'. This can be overridden during container runtime if needed.
USER node
Switches to a non-root user. This is a security best practice to minimize the impact of security vulnerabilities.
WORKDIR /usr/src/app
Sets the working directory for subsequent instructions in this stage.
COPY package*.json ./
Copies package.json and package-lock.json from the host to the container.
COPY .env ./
Copies .env file from the host to the container.
RUN npm ci --production
Installs only production dependencies, skipping development dependencies.
COPY --from=builder /usr/src/app/dist ./dist
Copies the compiled/transpiled application from the previous build stage into the current stage.
EXPOSE 3030
Exposes port 3030 for incoming connections. Note that this doesn't actually publish the port; it's just a documentation of intended port usage.
CMD ["node", "dist/main/server.js"]
Specifies the default command to run when the container starts. In this case, it runs the Node.js application server from the compiled output.
- Build docker image:
docker build -t devlukas/newsletter-sender:1.0 .
- Run docker container:
docker run -p 3030:3030 devlukas/newsletter-sender:1.0
Unsubscribe link into de the email;- Add a weekly email schedule (example: node-cron)
-
Questions and feedbacks are very welcome.
-
Email: lukas.veiga10@gmail.com