Developing a JavaScript boilerplate

August 06, 2024

It all started with the Hackathon Starter that has limitations. I also wanted to follow The Node.js best practices list.

TypeScript in the backend became a modern necessity.

Some project ideas I had revolved around GraphQL and i wanted to reuse the business logic.

Keeping costs low at prototyping was a must. Back in the day we could spin up Heroku dynos for free but that stopped at the end of 2022 and I couldn't adapt my projects to Render or Fly.io.

On my work experience we moved from Bull to Amazon SQS, Redis to Amazon DynamoDB, AWS API Gateway WebSocket APIs over some Redis backed solution like Socket.IO.

Netlify Functions or Next API routes could be a viable option sometimes.

I tried using NestJS but it had a few problems.

Nest ConfigService can't be loaded async and I wanted to load my secrets from AWS Secrets Manager to use secret rotation.

The JwtService registerAsync didn't work.

OpenAPI had some TypeScript problems.

Nest in serverless functions is not a good idea and Datadog APM will become ugly with the COLD START insight on every function.

In Serverless scenarios using Atlas Data API was simpler than Nest with Mongoose for MongoDB.

I wouldn't be using much more of what the framework offers like the authentication, WebSockets, caching, file upload, messaging or static serving solutions.

There's to much abstraction for simple things. I would need to create a module or install @nestjs-modules/mailer just to use nodemailer the way the framework intended.

I also wanted to define the actions an user could execute on my system in a straightforward way. The AWS CLI was a big inspiration.

In the typical auth flow, the user must first signup or createAccount, then he can login or in case he forgot his password he can sendPasswordRecoveryEmail and resetPassword. Then he must verifyAccount or in case he accidentally deleted sendAccountVerificationEmail again. Finally he can deleteAccount.

The process of thinking how this functions would be encapsulate around “services” and “modules” and which dependencies I should inject from the beginning just slowed things down and I also didn't wanted to just put everything on AppService.

I ended up relying heavily on zod Functions to make sure my business logic was encapsulated.

Creating a custom Data Access Object for interacting with Atlas Data API similarly how I would with Mongoose.

Using a express app inside a AWS Lambda function with @codegenie/serverless-express or apollo-server-lambda both which easily allow me to put the same code inside a Dockerfile and scale or isolate the specific functions as needed.


Written by João Oliveira in his brief moments of clarity.