<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Fedor Rychkov</title>
    <description>The latest articles on Forem by Fedor Rychkov (@stonedcatt).</description>
    <link>https://forem.com/stonedcatt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1847411%2F556a401d-fbfe-4ead-b1cc-4dc47052c59e.jpeg</url>
      <title>Forem: Fedor Rychkov</title>
      <link>https://forem.com/stonedcatt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/stonedcatt"/>
    <language>en</language>
    <item>
      <title>Minimal production-ready template for a Next.js app</title>
      <dc:creator>Fedor Rychkov</dc:creator>
      <pubDate>Mon, 02 Mar 2026 15:26:21 +0000</pubDate>
      <link>https://forem.com/stonedcatt/minimal-production-ready-template-for-a-nextjs-app-3hn</link>
      <guid>https://forem.com/stonedcatt/minimal-production-ready-template-for-a-nextjs-app-3hn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I'm a full-stack developer and have been doing web development since around 2014. In recent years I've been focusing more on in-house projects, freelance work, and my own micro-products. A constant pain point is the lack of a convenient, ideally free, way to spin up yet another project for a task without getting bogged down in infrastructure.&lt;/p&gt;

&lt;p&gt;Over several years I've brought a few things to a solid, reusable state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated deploy to a server (including blue/green);&lt;/li&gt;
&lt;li&gt;Issuing and renewing SSL certificates for the domain;&lt;/li&gt;
&lt;li&gt;A minimal metrics stack (Grafana, Prometheus, Loki, etc.) — optional, controlled by flags.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is two battle-tested boilerplates: Nest.js and Next.js. In this article I'm sharing the second one. To keep infrastructure simple, the template is built around a single stack: Next.js, auth, and database access within one application.&lt;/p&gt;

&lt;p&gt;This kind of template may not be anything new, but if it saves someone a few hours on deploy and environment setup, I'll be glad.&lt;/p&gt;

&lt;h2&gt;
  
  
  From idea to release
&lt;/h2&gt;

&lt;p&gt;Most developers want to take an idea to a working product — a side project, a micro-product, an internal service. I'll speak from my own experience: everyone's context and goals are different.&lt;/p&gt;

&lt;p&gt;A lot can be built today with AI and ready-made tutorials. Getting a product to a stable release without getting stuck on infrastructure is another story. That's why I published this standalone Next.js template: deploy, certificates, and optional metrics are already wired in. You can use it as a base and focus on your app logic.&lt;/p&gt;

&lt;p&gt;You're free to do whatever you want with the template: it's open. The repo includes the essentials — auth, cookies, database, scripts, and workflows for your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's inside
&lt;/h2&gt;

&lt;p&gt;The template is built for one scenario: one repo, one stack (Next.js + API routes + DB and sessions when needed). No separate infra “admin” layer — everything is driven by config and flags in GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy and environments&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workflows for stage and prod (separate files under &lt;code&gt;.github/workflows/&lt;/code&gt;), with shared logic in a reusable workflow.&lt;/li&gt;
&lt;li&gt;Support for “build on server” and “image from GHCR”; optionally blue/green (with some caveats).&lt;/li&gt;
&lt;li&gt;Deploy triggers on push to the right branch; only the containers you need are started on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Certificates and nginx&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let's Encrypt via certbot: initial issuance and renewal.&lt;/li&gt;
&lt;li&gt;Nginx as the single entry point: proxy to Next.js, and when metrics are enabled, a subpath for Grafana. The nginx config is generated from templates using the &lt;code&gt;METRICS_ENABLED&lt;/code&gt; flag: when metrics are off, Grafana blocks are omitted, so nginx doesn't depend on the Grafana container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Optional parts&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; — started only when &lt;code&gt;redis_enabled: true&lt;/code&gt; in the workflow config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics (Grafana, Prometheus, Loki, Telegraf, Promtail, etc.)&lt;/strong&gt; — only when &lt;code&gt;metrics_enabled: true&lt;/code&gt;. Both flags are off by default: only nginx and the app are always run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mongo&lt;/strong&gt; — is only started when &lt;code&gt;mongo_enabled: true&lt;/code&gt; is set in the workflow config. This database comes as the default in the template for now. You can always remove or skip the parts related to internal DB logic for the Next.js app, especially if you want to separate the final backend from the client application logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you can start with the minimum (app + nginx + certificates) and turn on cache and dashboards with a single change in &lt;code&gt;stage-deploy.yml&lt;/code&gt; / &lt;code&gt;prod-deploy.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auth and database&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The repo includes a minimal layer for auth and DB access within Next.js (API routes, cookies, sessions). If you need a separate backend, you can keep the same workflows and scripts and adapt the logic in &lt;code&gt;app/api/[...]/route&lt;/code&gt; and deploy flags to your setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who might find this useful
&lt;/h2&gt;

&lt;p&gt;Best fit: pet projects, internal services, and MVPs. Anyone who wants to get an idea to production quickly without diving deep into DevOps: one repo, clear flags, one server. And anyone already comfortable with Next.js who wants a ready-made base with deploy and optional metrics — with no extra services by default.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Fedorrychkov/nextjs-super-boilerplate" rel="noopener noreferrer"&gt;nextjs-super-boilerplate&lt;/a&gt;. If this approach fits you, feel free to use the template as a base for your next project or a quick release; I’d appreciate a star and feedback. Please do open issues if you run into problems or find bugs in the core setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond the deploy template
&lt;/h2&gt;

&lt;p&gt;The repo isn't just configs and scripts: it includes a full client app example. You get a cabinet-style flow with auth — email/password login, sign-up, protected routes, profile fetched from the server, and session handling via cookies. You can use it as a reference or as the starting point for your product.&lt;/p&gt;

&lt;p&gt;There's also a small UI kit in the codebase: buttons, inputs, typography, sidebar, badges, and other reusable components. They're used on the example pages (including a UI Kit demo page) and keep the look consistent. If you're on React and Next.js, you can build on this set and tweak it to your design instead of starting the UI from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  For those who read to the end
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How to use it
&lt;/h3&gt;

&lt;p&gt;A few options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clone&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Fedorrychkov/nextjs-super-boilerplate.git your-project-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You keep the existing git history; then set your own &lt;code&gt;origin&lt;/code&gt; (your new repo) and push there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fork&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open the &lt;a href="https://github.com/Fedorrychkov/nextjs-super-boilerplate" rel="noopener noreferrer"&gt;repository&lt;/a&gt; and click &lt;strong&gt;Fork&lt;/strong&gt;. You get a copy under your account that you can work on and push to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use as template&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the repo page, click &lt;strong&gt;Use this template&lt;/strong&gt; (next to Fork). GitHub creates a new repo under your account with no commit history — a clean start for your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local setup
&lt;/h3&gt;

&lt;p&gt;I use pnpm locally, but npm and yarn should work the same. Docker configs use npm. The repo has no package-lock — only pnpm-lock.yaml.&lt;/p&gt;

&lt;p&gt;After cloning or creating a repo from the template:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install dependencies: &lt;code&gt;pnpm install&lt;/code&gt; (or &lt;code&gt;npm install&lt;/code&gt; / &lt;code&gt;yarn&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;.env.example&lt;/code&gt; to &lt;code&gt;.env.local&lt;/code&gt; and set your values (JWT, MongoDB, etc.) if needed.&lt;/li&gt;
&lt;li&gt;For local MongoDB in a container: &lt;code&gt;make up-local&lt;/code&gt; (starts mongo from &lt;code&gt;docker-compose.dev.yml&lt;/code&gt;). Use the connection settings from &lt;code&gt;.env.local&lt;/code&gt; (e.g. &lt;code&gt;MONGO_HOST=localhost&lt;/code&gt; when the app runs on the host).&lt;/li&gt;
&lt;li&gt;Start the dev server: &lt;code&gt;pnpm run dev:local&lt;/code&gt; (or &lt;code&gt;npm run dev:local&lt;/code&gt;). The app will be at the URL shown (usually &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To stop local mongo: &lt;code&gt;make down-local&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of local Mongo (both in dev and on servers) you can use an external cluster: set the full connection string in &lt;code&gt;MONGO_URI&lt;/code&gt;. On deploy, set &lt;code&gt;mongo_enabled: false&lt;/code&gt; in the workflow so the mongo container is not started.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Workflows config parameters
&lt;/h3&gt;

&lt;p&gt;Parameters are set in &lt;code&gt;stage-deploy.yml&lt;/code&gt; and &lt;code&gt;prod-deploy.yml&lt;/code&gt; and passed into the shared &lt;code&gt;reusable-deploy-config.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inputs (with)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;domain&lt;/strong&gt; — Domain the app is deployed to (e.g. &lt;code&gt;app.example.com&lt;/code&gt;). Used by nginx and certbot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;api_env&lt;/strong&gt; — App environment: &lt;code&gt;stage&lt;/code&gt;, &lt;code&gt;prod&lt;/code&gt;. Used in env vars and env file names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;env_file&lt;/strong&gt; — Path to the env file on the server (e.g. &lt;code&gt;.env.stage&lt;/code&gt;, &lt;code&gt;.env.prod&lt;/code&gt;). Secrets are appended to this file during deploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nginx_mode&lt;/strong&gt; — nginx mode: &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;https&lt;/code&gt;. Chooses which config template and certificates are used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;certbot_test_mode&lt;/strong&gt; — Use Let's Encrypt staging server. Useful for testing without rate limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;migrations_run&lt;/strong&gt; — Run DB migrations on deploy. Not needed if you don't use DB migrations in the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;blue_green_enabled&lt;/strong&gt; — Enable blue/green deploy (spin up a green copy, validate, then switch with minimal downtime).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;deploy_mode&lt;/strong&gt; — Build strategy: empty/&lt;code&gt;default&lt;/code&gt; = build on server; &lt;code&gt;registry&lt;/code&gt; = build in CI, push image to GHCR, server only pulls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;node_version&lt;/strong&gt; — Node.js version used in GitHub Actions (e.g. 24). Set the same version in &lt;a href="https://github.com/Fedorrychkov/nextjs-super-boilerplate/blob/main/.docker/Dockerfile" rel="noopener noreferrer"&gt;.docker/Dockerfile&lt;/a&gt; (currently 24.13.1).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;registry_subname&lt;/strong&gt; — Name fragment for the GHCR image (e.g. &lt;code&gt;web&lt;/code&gt; → &lt;code&gt;ghcr.io/owner/web:sha&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;notigy_enabled&lt;/strong&gt; — Send Telegram notifications on deploy start/success/failure. You must provide bot token, chat id and optionally thread id. For groups/supergroups the id must be prefixed with &lt;code&gt;-100&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tag&lt;/strong&gt; — Optional tag for Telegram messages (e.g. project or environment name).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;redis_enabled&lt;/strong&gt; — Start the Redis container. If &lt;code&gt;false&lt;/code&gt;, redis is not started.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;metrics_enabled&lt;/strong&gt; — Start the metrics stack (Prometheus, Grafana, Loki, Telegraf, Promtail, etc.). If &lt;code&gt;false&lt;/code&gt;, nginx does not depend on Grafana.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mongo_enabled&lt;/strong&gt; — Start the MongoDB container. If &lt;code&gt;false&lt;/code&gt;, an external cluster is expected (connection via &lt;code&gt;MONGO_URI&lt;/code&gt; in env).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;certbot_email&lt;/strong&gt; — Email for Let's Encrypt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;grafana_admin_user&lt;/strong&gt;, &lt;strong&gt;grafana_admin_password&lt;/strong&gt; — Grafana admin login and password (when metrics are enabled).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Secrets&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;server_host&lt;/strong&gt;, &lt;strong&gt;server_username&lt;/strong&gt;, &lt;strong&gt;server_password&lt;/strong&gt; — Host, username and password for SSH to the deploy server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;env&lt;/strong&gt; — Env file contents (environment variables) appended to &lt;code&gt;env_file&lt;/code&gt; on the server. Usually different secrets for stage and prod.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;database_certificate&lt;/strong&gt; — Optional: DB certificate or key if you use a separate file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ghcr_username&lt;/strong&gt;, &lt;strong&gt;ghcr_token&lt;/strong&gt; — Login and token for GitHub Container Registry; required when &lt;code&gt;deploy_mode: registry&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tg_token&lt;/strong&gt;, &lt;strong&gt;tg_chat_id&lt;/strong&gt;, &lt;strong&gt;tg_thread_id&lt;/strong&gt; — Bot token, chat id and optional thread id for Telegram notifications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;grafana_admin_user&lt;/strong&gt;, &lt;strong&gt;grafana_admin_password&lt;/strong&gt; — Can be passed as secrets instead of plain values in &lt;code&gt;with&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full list and definitions see &lt;a href="https://github.com/Fedorrychkov/nextjs-super-boilerplate/blob/main/.github/workflows/reusable-deploy-config.yml" rel="noopener noreferrer"&gt;reusable-deploy-config.yml&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading
&lt;/h2&gt;

&lt;p&gt;I'm not claiming this is a one-size-fits-all solution, but I hope the template and configs help you ship ideas faster. Keep in mind: the setup isn't aimed at high load; use proper DB indexes. Auto-deploy can be tricky sometimes — e.g. when rebuilding existing containers or issuing certificates for the first time.&lt;/p&gt;

&lt;p&gt;Your VPS must be reachable from the internet: ensure the firewall allows ports 22, 80 and 443. You need to own or get a domain and point its DNS A record to the server IP. The template doesn't support using your own SSL certificates yet — only Let's Encrypt issuance is wired in.&lt;/p&gt;

&lt;p&gt;If the template was useful, a star on the &lt;a href="https://github.com/Fedorrychkov/nextjs-super-boilerplate" rel="noopener noreferrer"&gt;repo&lt;/a&gt; is appreciated. If something breaks, open an &lt;a href="https://github.com/Fedorrychkov/nextjs-super-boilerplate/issues" rel="noopener noreferrer"&gt;issue&lt;/a&gt; and I'll try to help. Good luck!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>buildinpublic</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Nestjs, Firebase, GCloud. How to Quickly Set Up an API Backend in TypeScript.</title>
      <dc:creator>Fedor Rychkov</dc:creator>
      <pubDate>Sat, 31 Aug 2024 19:55:01 +0000</pubDate>
      <link>https://forem.com/stonedcatt/nestjs-firebase-gcloud-how-to-quickly-set-up-an-api-backend-in-typescript-9no</link>
      <guid>https://forem.com/stonedcatt/nestjs-firebase-gcloud-how-to-quickly-set-up-an-api-backend-in-typescript-9no</guid>
      <description>&lt;p&gt;It's great that you decided to open this article. My name is Fedor, and I've been a full-stack developer on a permanent basis since the end of 2021. Just in case, here is my &lt;a href="https://github.com/Fedorrychkov/Fedorrychkov" rel="noopener noreferrer"&gt;github profile&lt;/a&gt;. In this brief article, I want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kickstart a small series of tutorials on setting up a backend API&lt;/li&gt;
&lt;li&gt;Provide an example of a NestJS project integrated with Firebase&lt;/li&gt;
&lt;li&gt;Help developers with a frontend background quickly set up an environment for backend development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This specific article is a step-by-step guide to Firebase integration with some nuances.&lt;/p&gt;

&lt;p&gt;I also want to warn the reader in advance: This and future articles are generally suitable for beginners, but you should be familiar with JavaScript/TypeScript in general, or at least not be afraid to Google things that are not covered in detail here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Nuances of working with Firebase&lt;/li&gt;
&lt;li&gt;Personal Experience&lt;/li&gt;
&lt;li&gt;Before Initialization&lt;/li&gt;
&lt;li&gt;My Setup&lt;/li&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;Env files&lt;/li&gt;
&lt;li&gt;Preparing to work with Firebase&lt;/li&gt;
&lt;li&gt;Project configuration for Connecting to Firebase&lt;/li&gt;
&lt;li&gt;Firestore Module&lt;/li&gt;
&lt;li&gt;Example Module&lt;/li&gt;
&lt;li&gt;Husky&lt;/li&gt;
&lt;li&gt;Testing and building indexes for api/example&lt;/li&gt;
&lt;li&gt;Firebase Storage&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Nuances of working with Firebase:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;At a certain point, you will need to set up a billing account in Google Cloud.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Integration of IaaS/PaaS solutions like Firebase doesn’t differ much from each other. Firebase is a conditionally free platform if you stay within the plan limits. Additionally, using Firebase imposes constraints on poorly designed code. In my experience, a mistake in designing the frequency of requests to Firestore resulted in a loss of $800 per day for the company during a surge in traffic. After some time debugging, we managed to reduce expenses by 30 times.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: The proposed stack is well-suited for pet projects and small production projects. The final boilerplate repo will include the necessary minimum configuration to start development. If you haven’t worked with Node.js and Nest.js before, you may encounter difficulties with some concepts and code constructs; I recommend preparing, for example, by reading &lt;a href="https://docs.nestjs.com/" rel="noopener noreferrer"&gt;Nestjs docs&lt;/a&gt; in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personal Experience
&lt;/h2&gt;

&lt;p&gt;Although I only started working full-stack a few years ago, I have already had the chance to try my hand at cross-functional projects on different stacks and platforms, mainly JS/TS. I got acquainted with NestJS when I joined a company that chose the path of isomorphic full-stack development, and since then, this path has stuck with me. Over the years of working on various projects, I managed to create a simple and minimal boilerplate for the next MVP startup or pet project. I sincerely hope that this article and the final repo will make your life easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before Initialization
&lt;/h2&gt;

&lt;p&gt;I will be writing this boilerplate from my perspective and environment, so first things first: &lt;em&gt;I work on the MacOS platform. All suggested actions should be cross-platform, but I do not rule out some difficulties.&lt;/em&gt; I welcome any questions and recommendations in the comments under this post.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Setup
&lt;/h3&gt;

&lt;p&gt;In the terminal, I use the ZSH shell instead of the standard BASH, so the first link is &lt;a href="https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH" rel="noopener noreferrer"&gt;ohmyzsh&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also need to keep different versions of Node.js due to the accumulation of projects of varying ages over the years, plus old habits die hard. For convenient work, I recommend installing &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;NVM&lt;/a&gt;, a version manager for Node.js.&lt;/p&gt;

&lt;p&gt;Lastly, since developers often have many projects, I use &lt;a href="https://pnpm.io/installation" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct Initialization
&lt;/h2&gt;

&lt;p&gt;Let's move on to the main set of commands and project configuration. First, let's install Node.js version 20 using nvm: &lt;code&gt;nvm i 20 &amp;amp;&amp;amp; nvm use 20&lt;/code&gt;, and then install pnpm: &lt;code&gt;npm i -g pnpm&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before proceeding, let's check that all packages are available in the terminal.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  my pnpm -v
9.6.0
➜  my nvm -v
0.39.2
➜  my npm -v
10.8.1
➜  my nvm ls
-&amp;gt;     v20.16.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Moving forward, install the Nest CLI globally: &lt;code&gt;npm i -g @nestjs/cli&lt;/code&gt;. After successfully installing the CLI, we can proceed to the project creation step. This is done using the command &lt;code&gt;nest new nestjs-startup-boilerplate&lt;/code&gt;, where you can replace &lt;code&gt;nestjs-startup-boilerplate&lt;/code&gt; with your project's name. Next, you will be prompted to select the project initialization configuration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Choose the package manager. I will select &lt;strong&gt;pnpm&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5mxahhjvbyfenlphqxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx5mxahhjvbyfenlphqxa.png" alt="Package manager to use"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 2: That's it for now; the main thing is to choose the package manager.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, you should have &lt;a href="https://github.com/Fedorrychkov/nestjs-startup-boilerplate/commit/33355292acb7111adca3afbd769fd2fc81694efd" rel="noopener noreferrer"&gt;this set of changes&lt;/a&gt; in your project's files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;Linting, tsconfig, and env files. Let's configure all of these.&lt;/p&gt;

&lt;p&gt;In any project, it's useful to have path aliases to avoid frequently seeing imports like &lt;code&gt;import something from '../../../../modules/something'&lt;/code&gt;. I prefer something like this: &lt;code&gt;import something from '~/modules/something'&lt;/code&gt;. It's much more readable and easier to maintain. We'll make a few changes to the tsconfig file; I won't go into detail here, but you can read more about tsconfig settings &lt;a href="https://www.typescriptlang.org/tsconfig/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea1xxzzktqsd9nemalny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea1xxzzktqsd9nemalny.png" alt="tsconfig changes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, I want to make changes to eslint.js and add my preferred configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7v7kawhplqv7pru7u9ie.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7v7kawhplqv7pru7u9ie.png" alt="eslint changes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's update the package.json by adding the following commands to the scripts section:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...,
scripts: {
  ...,
  "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
  "lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
},
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Be sure to install a new dependency (this is a plugin for the eslint configuration):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pnpm add -D eslint-plugin-simple-import-sort
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, let's configure the prettier file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdyw7e7336x1mfg97v7cr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdyw7e7336x1mfg97v7cr.png" alt="Prettier changes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ENV Files
&lt;/h3&gt;

&lt;p&gt;First, let's add our .env.example file. In this file, we'll give developers an idea of which variables can be configured in the project. For now, it looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SA_KEY=path_to_service_file

MINI_APP_URL=domain_to_mini_app # we'll leave this as a placeholder for future use.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Make sure to configure the .gitignore file by adding the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# all env files except example
.env*
!.env.example

# google service account file (by firebase)
service-account*.json

# firebase config file
.firebaserc

# tg library local cache file
sessions.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/Fedorrychkov/nestjs-startup-boilerplate/commit/0e3f830d2b55137a9e72e33b32673210cf273642" rel="noopener noreferrer"&gt;Here is the commit with these changes&lt;/a&gt;. If we also run the command &lt;code&gt;pnpm run lint:fix&lt;/code&gt;, we'll get the &lt;a href="https://github.com/Fedorrychkov/nestjs-startup-boilerplate/commit/95a73d71132db09df39cef8a8734339893235cde" rel="noopener noreferrer"&gt;linted files&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At this stage, the basic project configuration is ready. Next, we'll set up the Firebase project through the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing to Work with Firebase
&lt;/h2&gt;

&lt;p&gt;In my projects, I usually use Firebase as a provider for Google Cloud Storage, Firestore Database, and Firebase Auth. Let's set up the first two services.&lt;/p&gt;

&lt;p&gt;First, open the &lt;a href="https://console.firebase.google.com/u/2/" rel="noopener noreferrer"&gt;Firebase project console&lt;/a&gt;. If you've never worked with Firebase before, you'll have a mostly empty dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzdffib6nup1u3rujbek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzdffib6nup1u3rujbek.png" alt="Empty Firebase Console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on "Get Started" to begin creating a project. On the first screen, you'll be asked to enter a project name; I'll name mine: &lt;code&gt;nestjs-boilerplate-example&lt;/code&gt;. This name will be used in the future for configuring the project's env files. On the second screen, you'll be asked to enable analytics; I don't need it, so I decline and create the project. After successful creation, the project will appear on your dashboard, and you can proceed by clicking "Continue" on the project creation confirmation screen.&lt;/p&gt;

&lt;p&gt;In the dashboard of the created project, you'll see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnh8mo2enttwqv81o0b9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnh8mo2enttwqv81o0b9k.png" alt="Firebase Project Console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So far, so simple. Next, we need to enable the services: Firestore, Authentication, and Storage. These sections can be found on the left sidebar of the console, under the "Build" section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzzf68vqks1kwvkc7keyj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzzf68vqks1kwvkc7keyj.png" alt="Firebase Project Build"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you open each section, you'll first see a prompt to enable the functionality. Click "Get Started," "Create Database," etc. Up to certain usage limits, these services are free. &lt;a href="https://firebase.google.com/pricing" rel="noopener noreferrer"&gt;More about pricing and limits&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Creating a database will require you to choose a server location; you can choose whichever is convenient for you depending on where your users are located. I usually choose Europe (Eur3). You can also select the launch mode; it's safe to leave the database in production mode. &lt;em&gt;Note: I’ve tried us-central1 and eur3, and haven’t noticed any significant difference in runtime speed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now you have a fully set up Firebase project. By the way, you can easily generate multiple Firebase projects for different environments (Production/Stage/Local/Test); for most of my projects, this setup is sufficient.&lt;/p&gt;

&lt;p&gt;Let's move on to the next step. We need to obtain the necessary data to run the project. To do this, go to your &lt;a href="https://console.firebase.google.com/u/2/project/nestjs-boilerplate-example/settings/general" rel="noopener noreferrer"&gt;Project Settings (This link is for my project, you won't be able to open it, but you can use it as an example)&lt;/a&gt; (located in the popover menu by clicking on the gear icon).&lt;/p&gt;

&lt;p&gt;Once in the project settings, select the "Service Accounts" tab and click the &lt;strong&gt;Generate New Private Key&lt;/strong&gt; button. This will allow you to download a .json file. We'll need this file to connect to Firebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Configuration for Connecting to Firebase
&lt;/h2&gt;

&lt;p&gt;Now, let's set up the environment variables.&lt;br&gt;
Move the previously downloaded Firebase service JSON file to the root of your project, and, for example, name it service-account.json.&lt;br&gt;
Add the service file name to your &lt;code&gt;.env.dev&lt;/code&gt; file in the field &lt;code&gt;SA_KEY=service-account.json&lt;/code&gt;. The &lt;code&gt;.env.dev&lt;/code&gt; file can be created based on the example in &lt;code&gt;.env.example&lt;/code&gt;. Also, update the commands in the scripts section of your &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
"scripts": {
  ...
  "build:prod": "NODE_ENV=production nest build",
  "start": "NODE_ENV=production nest start",
  "start:dev": "NODE_ENV=development nest start --watch"
  "start:prod": "NODE_ENV=production node dist/main"
  ...
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In essence, we just added the &lt;code&gt;NODE_ENV&lt;/code&gt; variable. This value can then be accessed via &lt;code&gt;process.env.NODE_ENV&lt;/code&gt;. It's better to pass other environment variables through specific &lt;code&gt;.env&lt;/code&gt; files. As an example, let's also add an &lt;code&gt;.env&lt;/code&gt; file now (you can create a copy from &lt;code&gt;.env.dev&lt;/code&gt;, which we'll use later for the production environment).&lt;/p&gt;

&lt;p&gt;Next, let's connect our environment variables in the project code.&lt;br&gt;
We’ll need two files: &lt;code&gt;app.module.ts&lt;/code&gt; and &lt;code&gt;main.ts&lt;/code&gt;.&lt;br&gt;
Let's start with the &lt;code&gt;src/env.ts&lt;/code&gt; file and fill it with some basic logic and flags from &lt;code&gt;process.env.NODE_ENV&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const ENV = process.env.NODE_ENV

export const isDevelop = ENV === 'development'
export const isProduction = ENV === 'production'

export const getEnvFile = () =&amp;gt; {
  if (isDevelop) {
    return '.env.dev'
  }

  return '.env'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note: The &lt;code&gt;NODE_ENV&lt;/code&gt; value passed at runtime can be accessed immediately. Other values will be loaded using &lt;code&gt;ConfigService&lt;/code&gt; in &lt;code&gt;AppModule&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We'll also need a library to read and load environment variables: &lt;code&gt;pnpm add @nestjs/config&lt;/code&gt;.&lt;br&gt;
Now we can make some changes to &lt;code&gt;app.module.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Module } from '@nestjs/common'

import { AppController } from './app.controller'
import { AppService } from './app.service'
import { ConfigModule } from '@nestjs/config'
import { getEnvFile } from './env'

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: getEnvFile(),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This way, environment variables can be used throughout the project.&lt;/p&gt;

&lt;p&gt;Now we can add the step to connect to Firebase. For this, we need to install the package &lt;code&gt;pnpm add firebase-admin&lt;/code&gt;. Open the &lt;code&gt;main.ts&lt;/code&gt; file and add the Firebase Admin initialization.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ConfigService } from '@nestjs/config'
import { NestFactory } from '@nestjs/core'
import * as admin from 'firebase-admin'
import * as fs from 'fs'

import { AppModule } from './app.module'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  const configService: ConfigService = app.get(ConfigService)
  // Retrieve the service file name from the env file
  const accountPath = configService.get&amp;lt;string&amp;gt;('SA_KEY')
  // You can refine the type definitions for the service file; as you can see, I haven't focused on that yet
  const serviceAccount: any = JSON.parse(fs.readFileSync(accountPath, 'utf8'))

  const adminConfig: admin.ServiceAccount = {
    projectId: serviceAccount.project_id,
    privateKey: serviceAccount.private_key,
    clientEmail: serviceAccount.client_email,
  }

  admin.initializeApp({
    credential: admin.credential.cert(adminConfig),
    databaseURL: `https://${adminConfig.projectId}.firebaseio.com`,
  })

  // The port value can also be moved to the env file if needed; I’ve skipped this step
  await app.listen(8080)
}

bootstrap()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So, the &lt;a href="https://github.com/Fedorrychkov/nestjs-startup-boilerplate/commit/ba9bf254910bd62c3b8e73c795d1830677e4971b" rel="noopener noreferrer"&gt;Firebase connection&lt;/a&gt; to the project is set up.&lt;/p&gt;

&lt;p&gt;But that's not all! Next, we will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Firestore module&lt;/li&gt;
&lt;li&gt;Examples of data models with controllers and services&lt;/li&gt;
&lt;li&gt;Adding the gcloud bucket module for file operations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Firestore Module
&lt;/h2&gt;

&lt;p&gt;Let's start with the module itself. Create a folder &lt;code&gt;src/providers/firestore&lt;/code&gt;. In this folder, we will organize the necessary code for connecting to Firestore collections.&lt;/p&gt;

&lt;p&gt;First, install the Firestore package: &lt;code&gt;pnpm add @google-cloud/firestore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;firestore&lt;/code&gt; folder, add 4 files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;firestore.module.ts&lt;/code&gt; - for connecting to the project's &lt;code&gt;AppModule&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;firestore.providers.ts&lt;/code&gt; - for listing Firestore entity documents&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;types.ts&lt;/code&gt; - for typing the module&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.ts&lt;/code&gt; - for cleanly re-exporting the Firestore module&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// firestore.providers.ts
export const FirestoreDatabaseProvider = 'firestoredb'
export const FirestoreOptionsProvider = 'firestoreOptions'
export const FirestoreCollectionProviders: string[] = [/* Next, you will need to add classes for Firestore collection documents.
 */]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// types.ts
// Add a type for typing the module arguments
import { Settings } from '@google-cloud/firestore'

export type FirestoreModuleOptions = {
  imports: any[]
  useFactory: (...args: any[]) =&amp;gt; Settings
  inject: any[]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// firestore.module.ts
// Here, we are creating our own module provider for Firestore collections
import { Firestore } from '@google-cloud/firestore'
import { DynamicModule, Module } from '@nestjs/common'

import {
  FirestoreCollectionProviders,
  FirestoreDatabaseProvider,
  FirestoreOptionsProvider,
} from './firestore.providers'
import { FirestoreModuleOptions } from './types'

@Module({})
export class FirestoreModule {
  static forRoot(options: FirestoreModuleOptions): DynamicModule {
    const collectionProviders = FirestoreCollectionProviders.map((providerName) =&amp;gt; ({
      provide: providerName,
      useFactory: (db) =&amp;gt; db.collection(providerName),
      inject: [FirestoreDatabaseProvider],
    }))

    const optionsProvider = {
      provide: FirestoreOptionsProvider,
      useFactory: options.useFactory,
      inject: options.inject,
    }

    const dbProvider = {
      provide: FirestoreDatabaseProvider,
      useFactory: (config) =&amp;gt; new Firestore(config),
      inject: [FirestoreOptionsProvider],
    }

    return {
      global: true,
      module: FirestoreModule,
      imports: options.imports,
      providers: [optionsProvider, dbProvider, ...collectionProviders],
      exports: [dbProvider, ...collectionProviders],
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, connect the module in &lt;code&gt;app.module.ts&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
import { ConfigModule, ConfigService } from '@nestjs/config'
...
import { FirestoreModule } from './providers'
...

@Module({
  imports: [
    FirestoreModule.forRoot({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) =&amp;gt; ({
        keyFilename: configService.get&amp;lt;string&amp;gt;('SA_KEY'),
      }),
      inject: [ConfigService],
    }),
  ],
  ...
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;At this stage, we are generally ready to create modules with controllers, repositories, and so on. Here is the &lt;a href="https://github.com/Fedorrychkov/nestjs-startup-boilerplate/commit/2163b22cb1270ecc254e603a332278a28fc93086" rel="noopener noreferrer"&gt;current set of changes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Module
&lt;/h2&gt;

&lt;p&gt;Finally, we get to creating an example module. We will implement basic CRUD operations using an abstract entity called &lt;code&gt;example&lt;/code&gt;. Specifically, I won’t implement the &lt;code&gt;delete&lt;/code&gt; method; its implementation is no different from other operations, except that in repository methods, we would call &lt;code&gt;.delete(documentId)&lt;/code&gt; and that’s it. Instead of deleting a document, you could implement a soft delete, which under the hood changes the &lt;code&gt;status&lt;/code&gt; field of the document (e.g., &lt;code&gt;status: ACTIVE/ARCHIVED&lt;/code&gt;). This example module won’t include that. &lt;/p&gt;

&lt;p&gt;In general, we will start by creating &lt;code&gt;src/modules&lt;/code&gt;, where we will set up the &lt;code&gt;example&lt;/code&gt; module. This entity is only for demonstrating how to organize project code. Of course, you are free to structure your code however you like.&lt;/p&gt;

&lt;p&gt;I suggest a structure for the modules as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/modules
  - example
    - repositories
      - example.repository.ts
      - index.ts
    - controllers
      - example.controller.ts
      - index.ts
    - services
      - example.service.ts
      - index.ts
    - entities
      - example.document.ts
      - index.ts
    - dto
      - example.request.ts
      - example.response.ts
      - index.ts
    example.module.ts
helpers
  - time.ts
  - id.ts
  - index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's get started. First, add the helpers, &lt;code&gt;pnpm add dayjs uuid&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// time.ts
import * as dayjs from 'dayjs'
import * as duration from 'dayjs/plugin/duration'
import * as isToday from 'dayjs/plugin/isToday'
import * as timezone from 'dayjs/plugin/timezone'
import * as utc from 'dayjs/plugin/utc'

// In general, you can do without all of this; it depends on your project. I left it as an example.

dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(isToday)
dayjs.extend(duration)

export const time = dayjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// id.ts
import { v4 } from 'uuid'

const getUniqueId = (): string =&amp;gt; v4()

export { getUniqueId }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, let's focus on the files for the Example module. I won’t change the name of the module or the table, but I suggest using &lt;code&gt;example&lt;/code&gt; as an abstraction of a post, including properties such as title, text, published flag, and additional attributes, along with the document ID and creation and last update dates.&lt;/p&gt;

&lt;p&gt;For dates, Firestore has a suitable model called &lt;code&gt;Timestamp&lt;/code&gt;, which is quite easy to work with and filter documents by. For the document ID, we will use the &lt;code&gt;uuid/v4&lt;/code&gt; utility.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// example/entities/example.document.ts
import { Timestamp } from '@google-cloud/firestore'

export class ExampleDocument {
  static collectionName = 'example'

  id: string
  title: string
  text?: string | null
  isPublished: boolean
  createdAt?: Timestamp | null
  updatedAt?: Timestamp | null
}

// example/dto/example.filter.ts
export class ExampleFilter {
  public isPublished?: boolean
}

// example/dto/example.request.ts
export class ExampleRequestBody {
  public title: string
  public text?: string
}

// example/dto/example.response.ts
export class ExampleResponseItem {
  public id: string
  public title: string
  public text?: string | null
  public isPublished: boolean
  public createdAt?: string | null
  public updatedAt?: string | null
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's add our controller with the following methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve a list of &lt;code&gt;example&lt;/code&gt; documents with query parameters for filtering by the &lt;code&gt;isPublished&lt;/code&gt; flag and without filtering&lt;/li&gt;
&lt;li&gt;Retrieve a single &lt;code&gt;example&lt;/code&gt; document by ID&lt;/li&gt;
&lt;li&gt;Create a new &lt;code&gt;example&lt;/code&gt; document&lt;/li&gt;
&lt;li&gt;Edit an existing document&lt;/li&gt;
&lt;li&gt;Update the &lt;code&gt;isPublished&lt;/code&gt; flag (there will be one method to toggle the flag without parameters)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// example/controllers/example.controller.ts
import { Body, Controller, Get, NotFoundException, Param, Patch, Post, Query } from '@nestjs/common'

import { ExampleFilter, ExampleRequestBody } from '../dto'
import { ExampleDocument } from '../entities'
import { ExampleService } from '../services'

@Controller('v1/example')
export class ExampleController {
  constructor(private readonly exampleService: ExampleService) {}

  @Get('/')
  async getList(@Query() query?: ExampleFilter): Promise&amp;lt;ExampleDocument[]&amp;gt; {
    const response = await this.exampleService.getList(query)

    if (!response?.length) {
      throw new NotFoundException('Examples are not exist')
    }

    return response
  }

  @Get('/:id')
  async get(@Param('id') id: string): Promise&amp;lt;ExampleDocument&amp;gt; {
    const response = await this.exampleService.getItem(id)

    if (!response) {
      throw new NotFoundException('Example does not exist')
    }

    return response
  }

  @Post('/')
  async create(@Body() body: ExampleRequestBody): Promise&amp;lt;ExampleDocument&amp;gt; {
    return this.exampleService.create(body)
  }

  @Patch('/:id')
  async update(@Param('id') id: string, @Body() body: ExampleRequestBody): Promise&amp;lt;ExampleDocument&amp;gt; {
    return this.exampleService.update(id, body)
  }

  @Patch('/publish/:id')
  async togglePublish(@Param('id') id: string): Promise&amp;lt;ExampleDocument&amp;gt; {
    return this.exampleService.togglePublish(id)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Most of the implementation is delegated to the &lt;code&gt;ExampleService&lt;/code&gt;. Let's add that as well.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// example/services/example.service.ts

import { Injectable, NotFoundException } from '@nestjs/common'

import { ExampleFilter, ExampleRequestBody } from '../dto'
import { ExampleRepository } from '../repositories'

@Injectable()
export class ExampleService {
  constructor(private readonly exampleRepository: ExampleRepository) {}

  public async getList(filter: ExampleFilter) {
    return this.exampleRepository.find(filter)
  }

  public async getItem(id: string) {
    return this.exampleRepository.getDataByDocumentId(id)
  }

  public async create(body: ExampleRequestBody) {
    return this.exampleRepository.create(body)
  }

  public async update(id: string, body: ExampleRequestBody) {
    const { doc, data } = await this.exampleRepository.getUpdate(id)

    if (!doc || !data) {
      throw new NotFoundException('Example document does not exist')
    }

    const response = this.exampleRepository.getValidProperties({ ...data, ...body }, true)
    const changedKeys = Object.keys(body)
    const valuesToUpdate: Partial&amp;lt;ExampleRequestBody&amp;gt; = {}

    for (const key of changedKeys) {
      const newValue = response?.[key]
      const currentValue = doc?.[key]

      if (newValue !== currentValue) {
        valuesToUpdate[key] = newValue
      }
    }

    if (Object.keys(valuesToUpdate).length &amp;gt; 0) {
      await doc.update({ ...valuesToUpdate, updatedAt: response?.updatedAt })
    }

    return response
  }

  public async togglePublish(id: string) {
    const { doc, data } = await this.exampleRepository.getUpdate(id)

    if (!doc || !data) {
      throw new NotFoundException('Example document does not exist')
    }

    const newPublishedState = !data?.isPublished

    const response = this.exampleRepository.getValidProperties({ ...data, isPublished: newPublishedState }, true)

    await doc.update({ isPublished: newPublishedState, updatedAt: response?.updatedAt })

    return response
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So far, only the &lt;code&gt;example.repository.ts&lt;/code&gt; file is shrouded in mystery; it is responsible for interacting with the Firestore database for the &lt;code&gt;Example&lt;/code&gt; collection. Let's add its implementation as well.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// example/repositories/example.repository.ts

import { CollectionReference, Query, Timestamp } from '@google-cloud/firestore'
import { Inject, Injectable, Logger } from '@nestjs/common'
import { getUniqueId, time } from 'src/helpers'

import { ExampleFilter } from '../dto'
import { ExampleDocument } from '../entities'

@Injectable()
export class ExampleRepository {
  private logger: Logger = new Logger(ExampleRepository.name)

  constructor(
    @Inject(ExampleDocument.collectionName)
    private collection: CollectionReference&amp;lt;ExampleDocument&amp;gt;,
  ) {}

  async getDataByDocumentId(id: string): Promise&amp;lt;ExampleDocument | null&amp;gt; {
    const snapshot = await this.collection.doc(id).get()

    if (!snapshot.exists) {
      return null
    } else {
      return snapshot.data()
    }
  }

  /**
  * The method returns the document value and additional methods for working with the collection document, with a particular focus on the .update(...props) method.
  **/
  async getUpdate(id: string) {
    const doc = await this.collection.doc(id)
    const snapshot = await doc.get()

    if (!snapshot.exists) {
      return { doc: null, data: null }
    } else {
      return { doc, data: snapshot.data() }
    }
  }

  /**
  * Essentially, this method will handle adding new filters to the collection query.
  * At the moment, it only uses the `isPublished` flag.
  **/
  private findGenerator(filter: ExampleFilter) {
    const collectionRef = this.collection
    let query: Query&amp;lt;ExampleDocument&amp;gt; = collectionRef

    if (typeof filter?.isPublished === 'boolean') {
      query = query.where('isPublished', '==', filter.isPublished)
    }

    return query
  }

  async find(filter: ExampleFilter): Promise&amp;lt;ExampleDocument[]&amp;gt; {
    const list: ExampleDocument[] = []
    const query = this.findGenerator(filter)

    const snapshot = await query.get()

    snapshot.forEach((doc) =&amp;gt; list.push(doc.data()))

    return list
  }

  async create(payload: Pick&amp;lt;ExampleDocument, 'title'&amp;gt; &amp;amp; Partial&amp;lt;ExampleDocument&amp;gt;) {
    const validPayload = this.getValidProperties(payload)
    const document = await this.collection.doc(validPayload.id)
    await document.set(validPayload)

    return validPayload
  }

  /**
  * This method is needed to prepare data before writing to Firestore.
  * The input document may be missing an ID and other fields, so we provide fallback values and set document timestamps.
  * The `newUpdatedAt` flag is used to set the current date in the `updatedAt` field.
  **/
  public getValidProperties(
    document: Omit&amp;lt;ExampleDocument, 'id' | 'isPublished'&amp;gt; &amp;amp; { id?: string; isPublished?: boolean | null },
    newUpdatedAt = false,
  ) {
    const dueDateMillis = time().valueOf()
    const createdAt = Timestamp.fromMillis(dueDateMillis)

    return {
      id: document.id || getUniqueId(),
      title: document.title,
      text: document.text ?? null,
      isPublished: document.isPublished ?? false,
      createdAt: document.createdAt ?? createdAt,
      updatedAt: newUpdatedAt ? createdAt : (document.updatedAt ?? null),
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Of course, in the &lt;code&gt;example/index.ts&lt;/code&gt; file, we will set up re-exporting of the module and document.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// example/index.ts
export * from './entities'
export * from './example.module'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we can update the &lt;code&gt;firebase.providers.ts&lt;/code&gt; file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// firebase.provider.ts
import { ExampleDocument } from 'src/modules/example'

...

// In this way, we add the document to the list of Firestore collections for working with `ExampleDocument`.
// In the future, you will need to add other new Firestore collections here.
export const FirestoreCollectionProviders: string[] = [ExampleDocument.collectionName]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To get the &lt;code&gt;example&lt;/code&gt; module working, add it to the &lt;code&gt;app.module.ts&lt;/code&gt; file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
import { ExampleModule } from './modules/example'
...

@Module({
  imports: [
    ...,
    ExampleModule,
  ],
  ...
})
export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It seems we forgot something. Working with Firebase without configuration files is not very convenient, especially when dealing with different environments. You will need to install the Firebase CLI globally: &lt;code&gt;npm install -g firebase-tools&lt;/code&gt;. After that, you will likely need to authenticate by running &lt;code&gt;firebase login&lt;/code&gt;. Once you complete the required steps, you can proceed to configure Firebase from the terminal.&lt;/p&gt;

&lt;p&gt;In the terminal, in the root of your project, you need to run the command &lt;code&gt;firebase init&lt;/code&gt;. This command will start the initialization process for a new or previously created Firebase project. Select Firestore from the options provided. If, like me, you have already created a Firebase project, choose &lt;code&gt;Use an existing project&lt;/code&gt;, find your project in the list, and select it. Continue the setup; usually, there’s no need to change anything further; you can keep the default file names as they are.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Selection is made by pressing the spacebar, and continuation is done by pressing enter/return (depending on your keyboard). Options are presented as hollow circles (essentially radio buttons).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note 2: Sometimes the required project may not appear. In that case, you can use the configuration files from the commit that will be at the end of these setup instructions.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You might also encounter a problem, like the one I faced: &lt;em&gt;Error: It looks like you haven't used Cloud Firestore in this project before. Go to &lt;a href="https://console.firebase.google.com/project/nestjs-boilerplate-example/firestore" rel="noopener noreferrer"&gt;https://console.firebase.google.com/project/nestjs-boilerplate-example/firestore&lt;/a&gt; to create your Cloud Firestore database.&lt;/em&gt; This can be resolved by adding a billing account. If you run &lt;code&gt;firebase init --debug&lt;/code&gt;, you can see the specific error.&lt;/p&gt;

&lt;p&gt;Here is my error log for initialization due to an inactive billing account:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;":{"code":403,"message":"Read access to project 'nestjs-boilerplate-example' was denied: please check billing account associated and retry","status":"PERMISSION_DENIED"}}
[2024-07-28T18:40:19.841Z] error getting database typeHTTP Error: 403, Read access to project 'nestjs-boilerplate-example' was denied: please check billing account associated and retry {"name":"FirebaseError","children":[],"context":{"body":{"error":{"code":403,"message":"Read access to project 'nestjs-boilerplate-example' was denied: please check billing account associated and retry","status":"PERMISSION_DENIED"}},"response":{"statusCode":403}},"exit":1,"message":"HTTP Error: 403, Read access to project 'nestjs-boilerplate-example' was denied: please check billing account associated and retry","status":403}
[2024-07-28T18:40:19.842Z] database_type: undefined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can set up a billing account on the page: &lt;a href="https://console.cloud.google.com/billing?authuser=2" rel="noopener noreferrer"&gt;GCP Billing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After creating a billing account in GCP and linking the project to this account, you should be able to complete the configuration without any further issues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1wx6ssgubid4tnb5l4b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1wx6ssgubid4tnb5l4b.png" alt="Firebase init selector"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After completing all the steps, the following files will be added to your project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.firebaserc&lt;/li&gt;
&lt;li&gt;firebase.json&lt;/li&gt;
&lt;li&gt;firestore.indexes.json - list of current indexes, which we edit in this file&lt;/li&gt;
&lt;li&gt;firestore.rules - contains the rules for working with Firestore. We do not change its value in the project dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: Instead of the &lt;code&gt;.firebaserc&lt;/code&gt; file, we keep its example, &lt;code&gt;.firebaserc.example&lt;/code&gt;, in the Git repository. How to set up CI/CD for all of this will be covered in the next article about deploying the backend on a VPS.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the future, we will be mainly interested in &lt;code&gt;.firebaserc&lt;/code&gt; (which will contain the Firebase project name) and &lt;code&gt;firestore.indexes.json&lt;/code&gt; (where you can configure your indexes and deploy them to the project using the command &lt;code&gt;firebase deploy --only firestore:indexes&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The final set of changes at this stage can be found in &lt;a href="https://github.com/Fedorrychkov/nestjs-startup-boilerplate/commit/b4b656287f3df64e30e7d3638f9e9d1dd001665d" rel="noopener noreferrer"&gt;this commit&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;At this stage, you can already work comfortably with the project and add the necessary indexes to Firestore, and so on. However, that’s not all. For example, I can’t live without Husky. Additional pre-commit hooks and many other settings can be configured through it. This is especially useful in team development for additional quality control. You can safely skip the next step if you don’t need it. The final boilerplate repository will already include all necessary settings for working with Husky. After setting up pre-commits, we will also review the current &lt;code&gt;api/example&lt;/code&gt; work and add the necessary indexes for the &lt;code&gt;example&lt;/code&gt; collection. I hope you’re not tired yet; let’s continue!&lt;/p&gt;

&lt;h1&gt;
  
  
  Continuing with Additions
&lt;/h1&gt;

&lt;p&gt;Having completed all the above steps, we now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A functional backend API that can be run locally and tested using curl or, for example, Postman.&lt;/li&gt;
&lt;li&gt;A working connection to the Firebase project and Firebase/Firestore configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we want to discuss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up a Husky pre-commit hook to run code linter&lt;/li&gt;
&lt;li&gt;Reviewing the current CRUD operations and adding indexes&lt;/li&gt;
&lt;li&gt;Adding an API for uploading files to Google Cloud Storage (also known as Google Cloud Bucket)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Husky
&lt;/h2&gt;

&lt;p&gt;Follow the &lt;a href="https://typicode.github.io/husky/get-started.html" rel="noopener noreferrer"&gt;Husky Get Started documentation&lt;/a&gt; for detailed instructions.&lt;/p&gt;

&lt;p&gt;In your terminal, run the command &lt;code&gt;pnpm add -D husky&lt;/code&gt;, then initialize Husky by running &lt;code&gt;npx husky init&lt;/code&gt;. This will add a &lt;code&gt;.husky&lt;/code&gt; folder to your project, along with a &lt;code&gt;pre-commit&lt;/code&gt; file that will run during the commit stage. Let’s modify the &lt;code&gt;package.json&lt;/code&gt; to include a new command in the &lt;code&gt;scripts&lt;/code&gt; section.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
"scripts": {
  ...,
  "prepare": "node .husky/install.mjs"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, add the &lt;a href="https://typicode.github.io/husky/how-to.html" rel="noopener noreferrer"&gt;.husky/install.mjs file&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Skip Husky install in production and CI
if (process.env.NODE_ENV === 'production' || process.env.CI === 'true') {
  process.exit(0)
}
const husky = (await import('husky')).default
console.log(husky())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This script is needed to avoid installation errors with Husky after running the dependency installation command &lt;code&gt;npm i/pnpm i&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Additionally, we will need to edit the &lt;code&gt;.husky/pre-commit&lt;/code&gt; file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pnpm run lint-staged &amp;amp;&amp;amp; pnpm run lint:fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Since we need to run a hook that checks files, you also need to install the package &lt;code&gt;pnpm add -D lint-staged&lt;/code&gt; and add additional configuration to &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This setup ensures that only the staged files are linted before committing, which helps maintain code quality and avoid committing files with linting errors.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
"scripts": {
  ...,
  "lint-staged": "lint-staged --allow-empty",
},
"lint-staged": {
  "*.{js,jsx,ts,tsx}": [
    "pnpm lint --fix"
  ]
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As a result, we will end up with &lt;a href="https://github.com/Fedorrychkov/nestjs-startup-boilerplate/commit/534ed4df51f77229ae7b93b35c45dee507be743e" rel="noopener noreferrer"&gt;this commit&lt;/a&gt;. From now on, all future commits will be validated for JavaScript/TypeScript files using ESLint/Prettier, and fixes will be applied for future commits using the previously added command &lt;code&gt;lint:fix&lt;/code&gt;. You can consider this pre-commit hook example as a foundation for your personal configurations. You can view the full list of available Git hooks in &lt;a href="https://git-scm.com/docs/githooks" rel="noopener noreferrer"&gt;githooks&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing and building indexes for api/example
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;app.module&lt;/code&gt; already includes an example working controller in &lt;code&gt;app.controller.ts&lt;/code&gt;, which can be accessed by sending a request to &lt;code&gt;http://localhost:8080&lt;/code&gt; from a browser or Postman. I will use Postman for a more convenient demonstration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xg16anjk3mnjvigiyto.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xg16anjk3mnjvigiyto.png" alt="Postman Main Get Route"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's make a few more requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  List of example documents.
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs24y91jsa4n7peekcgne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs24y91jsa4n7peekcgne.png" alt="Postman Example List Get Route"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a result, we will get the expected 404 error about missing documents. (This behavior is initially set in the controller code, you can configure it as you see fit)&lt;/p&gt;

&lt;p&gt;Now, let's request a list of examples with a filter in the query parameters &lt;code&gt;?isPublished=&amp;lt;false or true&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82i50h9xj7prmo2bxmul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82i50h9xj7prmo2bxmul.png" alt="Postman Example List Get Route With Wrong Filter"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, we sent a request with a parameter, but we get the same error. In fact, this is normal, but there is a problem. At the moment, our API cannot read and work with boolean values, they are displayed in controllers and services as string values 'true' | 'false'.&lt;br&gt;
If we log the incoming query argument in the GET v1/example controller, we will see the following picture&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ isPublished: 'false' }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;There are several ways to solve this problem.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;At the repository level, in the findGenerator method, manually convert the boolean string to the Boolean type.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Instead of
if (typeof filter?.isPublished === 'boolean') {
  query = query.where('isPublished', '==', filter.isPublished)
}

// Something like this
if (filter?.isPublished) {
  const isPublished = filter?.isPublished === 'true' ? true : false
  query = query.where('isPublished', '==', isPublished)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Add an additional step to transform them&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's try the second step. Edit the controller method.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// example/controllers/example.controller.ts
import {
  ...,
  ParseBoolPipe,
  ...,
} from '@nestjs/common'
...
  @Get('/')
  async getList(@Query('isPublished', ParseBoolPipe) isPublished?: boolean): Promise&amp;lt;ExampleDocument[]&amp;gt; {
    const response = await this.exampleService.getList({ isPublished })
    // ...
  }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Thus, we transform the incoming isPublished parameter into a boolean value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a new example document
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnt501ueqtsprvukf4zl2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnt501ueqtsprvukf4zl2.png" alt="Postman Example Create"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I created several documents as an example. Let's request our list again, with a filter for isPublished=false&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi7priialyilc2jtqsc6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi7priialyilc2jtqsc6d.png" alt="Postman Example Unpublished list"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you look closely, you can see that the list is being fetched correctly. However, for many collections, it's necessary to change directions or guarantee the return of a list, for example, taking into account the creation date by descending/ascending values.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// repositories/example.repository.ts

...
  async find(filter: ExampleFilter): Promise&amp;lt;ExampleDocument[]&amp;gt; {
    ...
    let query = this.findGenerator(filter)

    query = query.orderBy('createdAt', 'desc')
    ...
  }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After attempting to request the list method again, we will see that the response has changed to a 500 error. If we go to the console, we will see that Firestore has thrown an error about the absence of an index for such a list query.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9 FAILED_PRECONDITION: The query requires an index. You can create it here: &amp;lt;url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Feel free to follow this URL to the project console, where you'll see a suggestion to add a new index. Don't rush to add it from there (you can initiate index creation from the console, but I'd prefer to do this through the firestore.indexes.json file)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwtx7hq0dd0mktauvu5e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwtx7hq0dd0mktauvu5e.png" alt="Firebase index creation modal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to take the data from this modal and manually create a new index, it will look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// firestore.indexes.json
{
  "indexes": [
    {
      "collectionGroup": "example",
      "queryScope": "COLLECTION",
      "fields": [
        {
          "fieldPath": "isPublished",
          "order": "ASCENDING"
        },
        {
          "fieldPath": "createdAt",
          "order": "DESCENDING"
        }
      ]
    }
  ],
  "fieldOverrides": []
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;__name__&lt;/code&gt; field does not need to be specified in this config; during deployment, it is added automatically. It's also important to maintain the order of fields.&lt;/p&gt;

&lt;p&gt;After editing the file, feel free to run the deployment command &lt;code&gt;firebase deploy --only firestore:indexes&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After launching, in the same Firebase console, in the indexes tab, you will see your index with the status Building... You need to wait for it to build and then make a request for the list again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Example by ID
&lt;/h3&gt;

&lt;p&gt;Now we can check the method of getting an example document by id. I won't include a screenshot as this request should already work without problems. In my case, it's &lt;code&gt;http://localhost:8080/v1/example/7c8a5d30-beca-409a-8509-873616c80f5a&lt;/code&gt;, your ID may differ from mine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Editing Example Document by ID
&lt;/h3&gt;

&lt;p&gt;Let's check the method of editing an example document, I will edit the title.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwhfn3m9ofm0ndh7ilgfy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwhfn3m9ofm0ndh7ilgfy.png" alt="Postman edit example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we request the list or document by id again, we will also see the changed data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Changing the isPublished flag
&lt;/h3&gt;

&lt;p&gt;In addition, let's change the value of isPublished in the document by making a request to another endpoint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3ws019uusjuumqyr1yi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3ws019uusjuumqyr1yi.png" alt="Postman toggle publish example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you check the state of the list with isPublished=false or true, you will see changes in the returned data.&lt;/p&gt;

&lt;p&gt;I'm also attaching a link to the current set of API calls in Postman &lt;a href="https://github.com/Fedorrychkov/fedorrychkov/blob/main/articles/nestjs-boilerplate-startup/common/postman_example.json" rel="noopener noreferrer"&gt;json file&lt;/a&gt;. You can download it and import it into your Postman workspace for quick deployment of the request environment.&lt;/p&gt;

&lt;p&gt;Another &lt;a href="https://github.com/Fedorrychkov/nestjs-startup-boilerplate/commit/81e8040835da547f5f6a2367f51f5104f1ee64f4" rel="noopener noreferrer"&gt;commit&lt;/a&gt; with changes. At this point, I could stop, but we haven't yet covered the aspect of publishing files to GCloud Storage...&lt;/p&gt;

&lt;h2&gt;
  
  
  Firebase Storage
&lt;/h2&gt;

&lt;p&gt;Let's define in advance the aspects of working with Storage that I'm aware of.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nest.js provides &lt;a href="https://docs-nestjs.netlify.app/techniques/file-upload" rel="noopener noreferrer"&gt;documentation on file uploading&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Free Storage provides 5GB of storage, beyond this volume you'll have to pay monthly for each byte of data.&lt;/li&gt;
&lt;li&gt;The free Spark plan doesn't allow creating separate buckets, but we'll account for this case in the code. However, I recommend using the project's default bucket and resolving the files themselves by folders and subfolders in the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use cases for Storage&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading and writing a file to a temporary folder in the project root, &lt;code&gt;/uploads&lt;/code&gt; in our case&lt;/li&gt;
&lt;li&gt;Writing and deleting a file in Storage&lt;/li&gt;
&lt;li&gt;Generating a public link to the file, if such a need exists (by default, we will always provide a public link, you can rewrite or supplement the necessary piece of code according to your use cases, I will provide a working example)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started, we have N new files waiting for us.&lt;br&gt;
In the providers folder, next to firebase, let's add a new folder - bucket.&lt;br&gt;
In it, we will create, of course, the bucket.module.ts file and a bunch of auxiliary ones. By the way, I almost forgot, let's install the packages &lt;code&gt;pnpm add @google-cloud/storage multer lodash&lt;/code&gt;, and necessarily &lt;code&gt;pnpm add -D @types/multer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Proposed structure:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/providers
  - bucket
    - providers
      default.bucket.ts
      index.ts
    bucket.constants.ts
    bucket.module.ts
    bucket.providers.ts
    bucket.shared.service.ts
    bucket.types.ts
    index.ts
    utils.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's describe each file, there will be relatively little code. And we'll add uploading and deleting to a separate endpoint for working with images via &lt;code&gt;api/example/:id/image&lt;/code&gt;. Let's start with the auxiliary files.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// default.bucket.ts
export class DefaultBucketProvider {
  static bucketName = 'default'
}

// bucket.constants.ts
export const getDefaultOptions = (role: string) =&amp;gt; ({
  entity: 'allUsers',
  role: role,
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;bucket.providers.ts represents the same concept as the firestore.providers.ts file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// bucket.providers.ts
import { DefaultBucketProvider } from './providers'

export const StorageBucketsProvider = 'StorageBucketsProvider'
export const StorageOptionsProvider = 'StorageOptionsProvider'

export const StorageBucketProviders: string[] = [DefaultBucketProvider.bucketName]

// bucket.types.ts
import { Bucket, Storage } from '@google-cloud/storage'

export type StorageProps = {
  keyFilename: string
}

export type FirestoreModuleOptions = {
  imports: any[]
  useFactory: (...args: any[]) =&amp;gt; StorageProps
  inject: any[]
}

export type BucketProvider = {
  bucket: Bucket
  storage: Storage
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Our service for working with Bucket.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// bucket.shared.service.ts
import { Bucket } from '@google-cloud/storage'
import { Logger } from '@nestjs/common'
import { extname } from 'path'

export class BucketSharedService {
  private bucket: Bucket
  private logger: Logger

  constructor(bucket: Bucket, logName?: string) {
    this.bucket = bucket
    this.logger = new Logger(`${BucketSharedService.name}_${logName}`)
  }

  public async isFileExists(name: string) {
    try {
      const fileName = name
      const file = this.bucket.file(fileName)

      const [isExists] = await file.exists()

      return isExists
    } catch (error) {
      throw error
    }
  }

  public async deleteFileByName(path: string, folderPath: string) {
    return new Promise(async (resolve, reject) =&amp;gt; {
      const pathes = path?.includes('%2F') ? path?.split('%2F') : path?.split('/')
      const fileName = pathes?.[pathes?.length - 1]
      const file = this.bucket.file(`${folderPath ? `${folderPath}/` : ''}${fileName}`)

      const [isExists] = await file.exists()

      if (!isExists) {
        reject(new Error('File does not exist'))
      }

      /**
       * There are no guarantees that it definitely deletes, but it seems that files become inaccessible for search by their links
       */
      file
        .delete()
        .then((res) =&amp;gt; {
          resolve(res)
        })
        .catch((err) =&amp;gt; {
          this.logger.error('Error with file bucket removing', err?.message)
          reject(err)
        })
    })
  }

  /**
   * Using with internal upload folders
   */
  public async saveFileByUploadsFolder(definedFile: Express.Multer.File, folderPath?: string): Promise&amp;lt;string&amp;gt; {
    const uniqueSuffix = `${folderPath || 'main'}/${Date.now()}-${Math.round(Math.random() * 1e9)}`
    const fileName = `${uniqueSuffix}${extname(definedFile.path)}`

    return new Promise((resolve, reject) =&amp;gt; {
      this.bucket
        .upload(definedFile.path, {
          destination: fileName,
        })
        .then((response) =&amp;gt; {
          const [uploadedFile] = response || []
          const file = this.bucket.file(uploadedFile?.metadata?.name)

          file.makePublic(async (err) =&amp;gt; {
            if (err) {
              this.logger.error(`Error making file public: ${err}`)
              reject(err)
            } else {
              this.logger.log(`File ${file.name} is now public.`)
              const publicUrl = file.publicUrl()
              this.logger.log(`Public URL for ${file.name}: ${publicUrl}`)
              resolve(publicUrl)
            }
          })

          return true
        })
        .catch((err) =&amp;gt; {
          reject(err)
        })
    })
  }

  /**
   * Using without multer storage option, only memory buffer
   */
  public async saveFileByUrlAndBuffer(path: string, folderPath: string, buffer: Buffer): Promise&amp;lt;string&amp;gt; {
    return new Promise(async (resolve, reject) =&amp;gt; {
      const uniqueSuffix = `${folderPath || 'main'}/${Date.now()}-${Math.round(Math.random() * 1e9)}`
      const fileName = `${uniqueSuffix}${extname(path)}`
      const file = this.bucket.file(fileName)
      await file.save(buffer)

      file.makePublic(async (err) =&amp;gt; {
        if (err) {
          this.logger.error(`Error making file public: ${err}`)
          reject(err)
        } else {
          this.logger.log(`File ${file.name} is now public.`)
          const publicUrl = file.publicUrl()
          this.logger.log(`Public URL for ${file.name}: ${publicUrl}`)
          resolve(publicUrl)
        }
      })
    })
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Adding a module with logic for connecting to Storage.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// bucket.module.ts
import { Storage } from '@google-cloud/storage'
import { DynamicModule, Module } from '@nestjs/common'
import * as fs from 'fs'

import { getDefaultOptions } from './bucket.constants'
import { StorageBucketProviders, StorageBucketsProvider, StorageOptionsProvider } from './bucket.providers'
import { FirestoreModuleOptions, StorageProps } from './bucket.types'

@Module({})
export class BucketModule {
  static forRoot(options: FirestoreModuleOptions): DynamicModule {
    const bucketProviders = StorageBucketProviders.map((providerName) =&amp;gt; ({
      provide: providerName,
      useFactory: async (storage: Storage) =&amp;gt; {
        console.log(storage, 'storage')
        /**
         * Use default bucket name
         */
        const bucket = storage.bucket(providerName === 'default' ? `${storage.projectId}.appspot.com` : providerName)

        const [isExist] = await bucket.exists()

        /**
         * Basic steps to create public bucket with writer rules, available only for BLAZE price
         */
        if (!isExist) {
          const options = getDefaultOptions(storage.acl.WRITER_ROLE)

          await bucket.create().catch((err) =&amp;gt; console.error(`bucket ${providerName} creation get error`, err))

          console.info(`bucket ${providerName} created successfully`)

          bucket.acl.add(options, (err) =&amp;gt; {
            if (!err) {
              console.info(`acl added successfully to ${providerName} bucket`)
            } else {
              console.error(`bucket ${providerName} error`, err)
            }
          })
        }

        return { bucket, storage }
      },
      inject: [StorageBucketsProvider],
    }))

    const optionsProvider = {
      provide: StorageOptionsProvider,
      useFactory: options.useFactory,
      inject: options.inject,
    }

    const provider = {
      provide: StorageBucketsProvider,
      useFactory: (config: StorageProps) =&amp;gt; {
        const serviceAccount: { project_id?: string } = JSON.parse(fs.readFileSync(config.keyFilename, 'utf8'))

        return new Storage({ ...config, projectId: serviceAccount.project_id ?? '' })
      },
      inject: [StorageOptionsProvider],
    }

    return {
      global: true,
      module: BucketModule,
      imports: options.imports,
      providers: [optionsProvider, provider, ...bucketProviders],
      exports: [provider, ...bucketProviders],
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, we need configuration for uploading files from the API to the temporary folder /uploads.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// utils.ts
import { diskStorage } from 'multer'
import { extname } from 'path'

export const storage = diskStorage({
  destination: './uploads',
  filename: (_, file, cb) =&amp;gt; {
    // Proposed file name regeneration
    const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e9)}`
    cb(null, `${uniqueSuffix}${extname(file.originalname)}`)
  },
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The constant &lt;code&gt;storage&lt;/code&gt; will be needed to avoid overloading memory when working with uploaded images. If &lt;code&gt;diskStorage&lt;/code&gt; is not used, we may encounter a shortage of RAM as traffic grows.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// index.ts - for short imports
export * from './bucket.module'
export * from './bucket.shared.service'
export * from './bucket.types'
export * from './providers'
export * from './utils'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To successfully launch the bucket module, it needs to be imported into app.module.ts.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app.module.ts
...

@Module({
  imports: [
    ...
    BucketModule.forRoot({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) =&amp;gt; ({
        keyFilename: configService.get&amp;lt;string&amp;gt;('SA_KEY'),
      }),
      inject: [ConfigService],
    }),
  ],
  ...
})
export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's add a helper for file validation. In it, we will describe the allowed file extensions and sizes.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- helpers
  ...
  - fileValidation
    constants.ts
    utils.ts
    index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// constants.ts
export const listDefaultImageExt = 'image/png,image/jpeg,image/webp'

export const listPngAndJpegImageExt = 'image/png,image/jpeg'

export const IMG_MAX_SIZE_IN_BYTE = 716800 // 700kb

export const IMG_MAX_1MB_SIZE_IN_BYTE = 1048576 // 1mb

export const IMG_MAX_5MB_SIZE_IN_BYTE = 1048576 * 5 // 1mb

// utils.ts
import { reduce } from 'lodash'

export const getFileTypesRegexp = (ext: string): string =&amp;gt; reduce(ext.split(','), (acc, key) =&amp;gt; `${acc}|${key}`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now let's focus on the controller and the image handling method, along the way, we will add a new parameter to example.document.ts and example.repository.ts, detailed changes can be viewed in the commit at the end of this section.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// example/controllers/example.controller.ts
...

@Post('/:id/image')
@UseInterceptors(FileInterceptor('file', { storage, limits: { files: 1 } }))
async updateExampleImage(
  @UploadedFile(
    new ParseFilePipe({
      validators: [
        new MaxFileSizeValidator({ maxSize: IMG_MAX_1MB_SIZE_IN_BYTE }),
        new FileTypeValidator({ fileType: getFileTypesRegexp(listPngAndJpegImageExt) }),
      ],
    }),
  )
  file: Express.Multer.File,
  @Param('id') id: string,
): Promise&amp;lt;ExampleDocument&amp;gt; {
  return this.exampleService.updateImage(id, file)
}
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// example/services/example.service.ts
...
export class ExampleService {
  private bucketService: BucketSharedService

  constructor(
    private readonly exampleRepository: ExampleRepository,
    @Inject(DefaultBucketProvider.bucketName)
    private readonly bucketProvider: BucketProvider,
  ) {
    this.bucketService = new BucketSharedService(this.bucketProvider.bucket, ExampleService.name)
  }
...
  public async updateImage(id: string, file: Express.Multer.File) {
    try {
      const { doc, data } = await this.exampleRepository.getUpdate(id)

      if (!doc || !data) {
        throw new NotFoundException('Example document does not exist')
      }

      const imageUrl = await this.bucketService.saveFileByUploadsFolder(file, `example/${data?.id}`)

      // It is possible to add deletion through an additional check if (data?.imageUrl, but in general this is not necessary, for the case when the file is missing, we definitely wrap the delete method call in a try-catch block)
      try {
        /**
         * Try to remove previously file
         */
        await this.bucketService.deleteFileByName(data?.imageUrl, `example/${data?.id}`)
      } catch {}

      const response = this.exampleRepository.getValidProperties({ ...data, imageUrl }, true)

      await doc.update({ imageUrl, updatedAt: response?.updatedAt })

      /**
       * Need for deletion uploads file by file.path
       */
      fs.unlinkSync(file.path)

      return response
    } catch (error) {
      /**
       * Need for deletion uploads/ file path
       */
      fs.unlinkSync(file.path)

      throw error
    }
  }
...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In general, everything is fine with file uploads. We have added a simple example of uploading an image to the example document. Let's make a request and check that everything works correctly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgx9d3h7omhxzlunvpbg3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgx9d3h7omhxzlunvpbg3.png" alt="Postman upload image example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It can be noticed that the imageUrl field has been added to the model with a link from GCloud Storage. The full set of changes can be found in &lt;a href="https://github.com/Fedorrychkov/nestjs-startup-boilerplate/commit/e0e0f789b9459a55729dde26a7a06f3dbe1cd513" rel="noopener noreferrer"&gt;this commit&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I hope that with this article we have achieved the intended goal of describing the minimum project configuration with an example of code structure, working with Firestore and GCloud Bucket. The full example of a NestJS project can be found on my &lt;a href="https://github.com/Fedorrychkov/nestjs-firebase-startup-boilerplate" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. I hope that I have been able to clearly and with a sufficient amount of code describe the basic steps for generating a CRUD application. I would not like to stop at this article. I will be glad to receive feedback and criticism. The final repo can serve as a not bad example for your quick start in developing an MVP or pet project on Nest.js integrated with Firebase or any other PaaS solution.&lt;/p&gt;

&lt;p&gt;In the next articles, we will return to this boilerplate and try to do more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implement methods for working with authorization and authentication in Firebase in a Nest.js API (Based on this article and the current example project).&lt;/li&gt;
&lt;li&gt;Add Swagger for convenient viewing of contracts and API testing.&lt;/li&gt;
&lt;li&gt;Dive a little into deploying the resulting backend application and set up notifications in the team chat Telegram.&lt;/li&gt;
&lt;li&gt;Try to write a Telegram Bot based on the nestjs-startup-boilerplate.&lt;/li&gt;
&lt;li&gt;Create a Mini-App in conjunction with the resulting Telegram bot.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>nestjs</category>
      <category>firebase</category>
    </item>
  </channel>
</rss>
