<?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>DEV Community: Friedrich WT</title>
    <description>The latest articles on DEV Community by Friedrich WT (@friedrich482).</description>
    <link>https://dev.to/friedrich482</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%2F1276243%2F631c5d68-20db-4cff-98f7-e0df15be9363.png</url>
      <title>DEV Community: Friedrich WT</title>
      <link>https://dev.to/friedrich482</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/friedrich482"/>
    <language>en</language>
    <item>
      <title>How to Set Up Prisma and MongoDB with Docker Compose for Development</title>
      <dc:creator>Friedrich WT</dc:creator>
      <pubDate>Mon, 13 Apr 2026 15:52:02 +0000</pubDate>
      <link>https://dev.to/friedrich482/how-to-set-up-prisma-and-mongodb-with-docker-compose-for-development-ada</link>
      <guid>https://dev.to/friedrich482/how-to-set-up-prisma-and-mongodb-with-docker-compose-for-development-ada</guid>
      <description>&lt;p&gt;&lt;u&gt;Note&lt;/u&gt;: If you are here because you have this issue currently and you just want to copy and paste the code, click to jump directly there.&lt;/p&gt;

&lt;p&gt;About two years ago, I wanted to use Prisma in a project but with MongoDB as database. Don’t ask me why, it was one of my first projects and from the fresh tutorial I just watched, the guy used MongoDB, then it was my choice without really understanding why. And in my case, I had a well structured schema for the database, so a relational database like PostgreSQL would have been a better choice.  Also, my TypeScript orm of choice is now Drizzle, still I want to share about what was done. &lt;/p&gt;

&lt;p&gt;The problem I had was to connect Prisma and MongoDB running in a docker container (local database). Even now, the recommended way according to &lt;a href="https://www.prisma.io/docs/orm/core-concepts/supported-databases/mongodb" rel="noopener noreferrer"&gt;Prisma docs&lt;/a&gt; to do it is to use MongoDB Atlas. This is because the MongoDB Prisma connector requires a replica set deployment for its transactions, and MongoDB Atlas provides such an environment out of the box. But what if you want everything locally (for development or testing for example), or if you want to be able to replicate that infrastructure without any external provider? This is exactly what we are going to do, using Docker Compose. We will use a Next.js application, but any other web framework would be fine.&lt;/p&gt;

&lt;p&gt;Let’s start by defining what a replica set is. It appears more in Kubernetes jargon, but basically a replica set is a group of instances (database in our case) that maintain the same dataset. We replicate our data across multiple nodes to guarantee their availability. Instead of having one single database instance we will have for example three of them and if one goes down, the others will still work. In the case of MongoDB, the three base nodes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;primary&lt;/strong&gt;: all writes to the database happen here&lt;/li&gt;
&lt;li&gt;one or many &lt;strong&gt;secondary(ies)&lt;/strong&gt;: they replicate the data of the primary node and in case the primary node goes down, a secondary node will be elected to become the new primary node, and the system can still work as expected&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;arbiter&lt;/strong&gt; that will be an mediator when a secondary node is chosen as primary. It doesn’t provide data redundancy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick excalidraw draft of our replica set:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpqa2sloya0k95mw8do96.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpqa2sloya0k95mw8do96.png" alt="Simple Replica Set" width="800" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a very simplified description of how the MongoDB replica set works and if you want more details about MongoDB replication system, you can check their &lt;a href="https://www.mongodb.com/docs/manual/replication/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;OK, now that we have an overview of the system that is required by Prisma to handle those transactions, it is time to write some code. Let’s start by creating a new Next.js application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest prisma-mongodb &lt;span class="nt"&gt;--yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;cd&lt;/code&gt; in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;prisma-mongodb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And start the development server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have setup a basic Nextjs to illustrate all this. This is just some markup and it doesn’t really matter. &lt;/p&gt;

&lt;p&gt;You can find the exact state of the repo here : &lt;a href="https://github.com/Friedrich482/prisma-mongodb/tree/1bdb0f3ded34bdd1366658170d8504db7dfed049" rel="noopener noreferrer"&gt;Initial setup&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next step is to install Prisma itself. Currently, the version 7 doesn’t support MongoDB. We need to use the version 6.19 which is the latest version with proper support for MongoDB. &lt;/p&gt;

&lt;p&gt;We first install it as a dev dependency with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; prisma@6.19
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After, we also install the Prisma client (for database querying) and dotenv (to load the environment variables from a &lt;code&gt;.env&lt;/code&gt; file) &lt;/p&gt;

&lt;p&gt;Configure Nextjs for ESM compatibility. Make sure the following options are present in your &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ESNext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bundler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And enable ESM in the &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;module&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will now proceed to the initialization of Prisma ORM with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;npx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;prisma&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;npx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;prisma&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--datasource-provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;mongodb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;../generated/prisma&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those commands will allow us to generate a &lt;code&gt;prisma&lt;/code&gt; folder with a &lt;code&gt;schema.prisma&lt;/code&gt; file to configure the database connection and a &lt;code&gt;prisma.config.ts&lt;/code&gt; (configuration file).&lt;/p&gt;

&lt;p&gt;Now comes the fun part: craft the &lt;code&gt;DATABASE_URL&lt;/code&gt;. Currently with the commands we just ran, Prisma provides us a &lt;code&gt;.env&lt;/code&gt; file with a dummy value for this environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mongodb+srv://root:randompassword@cluster0.ab1cd.mongodb.net/mydb?retryWrites=true&amp;amp;w=majority"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is composed of some key elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Protocol (Here &lt;code&gt;mongodb+srv&lt;/code&gt;) &lt;/li&gt;
&lt;li&gt;Base URL (&lt;code&gt;root:randompassword@cluster0.ab1cd.mongodb.net&lt;/code&gt;) formed of the username of the database and its password then the host (which can also be something like &lt;code&gt;localhost:27017&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The database (&lt;code&gt;mydb&lt;/code&gt;) &lt;/li&gt;
&lt;li&gt;Additional parameters (&lt;code&gt;retryWrites=true&amp;amp;w=majority&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are going to create our own replica set with Docker Compose and update the &lt;code&gt;DATABASE_URL&lt;/code&gt; accordingly. Let’s start by creating a &lt;code&gt;compose.yaml&lt;/code&gt; file (or &lt;code&gt;docker-compose.yml&lt;/code&gt;):&lt;/p&gt;

&lt;h2 id="compose-code"&gt;Compose code&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# compose.yaml or docker-compose.yml&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mongodb-primary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitnami/mongodb@${MONGODB_DIGEST}&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb-primary&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db/.env.mongo.primary&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1:27017:27017&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;demo-prisma-db:/bitnami&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-network&lt;/span&gt;

  &lt;span class="na"&gt;mongodb-secondary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitnami/mongodb@${MONGODB_DIGEST}&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb-secondary&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongodb-primary&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db/.env.mongo.secondary&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1:27018:27017&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-network&lt;/span&gt;

  &lt;span class="na"&gt;mongodb-arbiter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitnami/mongodb@${MONGODB_DIGEST}&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb-arbiter&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongodb-primary&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db/.env.mongo.arbiter&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1:27019:27017&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-network&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;demo-prisma-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the proper .env files: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The root &lt;code&gt;.env&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MONGODB_DIGEST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sha256:4ff25cfca3d44751416fffcde9fc3847dcf49ace2998f244a3bf319d9e216125 &lt;span class="c"&gt;# I pinned the version of the image to a specific one&lt;/span&gt;
&lt;span class="nv"&gt;MONGODB_REPLICA_SET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rs0 &lt;span class="c"&gt;# The name of the replica set&lt;/span&gt;
&lt;span class="nv"&gt;MONGODB_REPLICA_SET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replicasetkey123 &lt;span class="c"&gt;# The replica set key, required for all our nodes. The current value is just an example&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A &lt;code&gt;db&lt;/code&gt; folder at the root of the repository, with three &lt;code&gt;.env&lt;/code&gt; files, each for one of the nodes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.env.mongo.primary&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="nv"&gt;MONGODB_REPLICA_SET_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;primary &lt;span class="c"&gt;# The replication mode   (the type of node)&lt;/span&gt;
 &lt;span class="nv"&gt;MONGODB_ADVERTISED_HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongodb-primary &lt;span class="c"&gt;# It will  make the primary node configured with a hostname instead of an ip address. The other nodes will refer to him as  "mongodb-primary"&lt;/span&gt;
 &lt;span class="nv"&gt;MONGODB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password &lt;span class="c"&gt;# The root password of the database, the current value is just an example&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.env.mongo.secondary&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MONGODB_REPLICA_SET_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secondary &lt;span class="c"&gt;# The replication mode (the type of node)&lt;/span&gt;
&lt;span class="nv"&gt;MONGODB_INITIAL_PRIMARY_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongodb-primary &lt;span class="c"&gt;# The hostname of our primary node&lt;/span&gt;
&lt;span class="nv"&gt;MONGODB_ADVERTISED_HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongodb-secondary &lt;span class="c"&gt;# The hostname of the secondary node&lt;/span&gt;
&lt;span class="nv"&gt;MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password &lt;span class="c"&gt;# The root password of the database&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.env.mongo.arbiter&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="nv"&gt;MONGODB_REPLICA_SET_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arbiter &lt;span class="c"&gt;# The replication mode (the type of node)&lt;/span&gt;
 &lt;span class="nv"&gt;MONGODB_INITIAL_PRIMARY_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongodb-primary &lt;span class="c"&gt;# The hostname of our primary node&lt;/span&gt;
 &lt;span class="nv"&gt;MONGODB_ADVERTISED_HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongodb-arbiter &lt;span class="c"&gt;# The hostname of the arbiter node&lt;/span&gt;
 &lt;span class="nv"&gt;MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password &lt;span class="c"&gt;# The root password of the database&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Now we should be able to start the replica set with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s check that it has properly started. For the primary node:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;exec&lt;/code&gt; in it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; mongo-primary bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;enter in &lt;code&gt;mongosh&lt;/code&gt; with the proper authentication credentials:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; mongosh &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; password 
   &lt;span class="c"&gt;# The root user is the default user of the db and can be  changed by setting `MONGODB_ROOT_USER`&lt;/span&gt;
   &lt;span class="c"&gt;# The password is the one we defined with  `MONGODB_ROOT_PASSWORD`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;check:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; db.isMaster&lt;span class="o"&gt;()&lt;/span&gt;.ismaster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should output true. &lt;/p&gt;

&lt;p&gt;For the secondary or arbitrary nodes we can repeat the same steps (exec and enter in mongosh) then check the content of our replica set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rs.status&lt;span class="o"&gt;()&lt;/span&gt;.members
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And see an output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;
    _id: 0,
    name: &lt;span class="s1"&gt;'mongodb-primary:27017'&lt;/span&gt;,
    health: 1,
    state: 1,
    stateStr: &lt;span class="s1"&gt;'PRIMARY'&lt;/span&gt;,
      ...
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="o"&gt;{&lt;/span&gt;
    _id: 1,
    name: &lt;span class="s1"&gt;'mongodb-secondary:27017'&lt;/span&gt;,
    health: 1,
    state: 2,
    stateStr: &lt;span class="s1"&gt;'SECONDARY'&lt;/span&gt;,
      ...
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="o"&gt;{&lt;/span&gt;
    _id: 2,
    name: &lt;span class="s1"&gt;'mongodb-arbiter:27017'&lt;/span&gt;,
    health: 1,
    state: 7,
    stateStr: &lt;span class="s1"&gt;'ARBITER'&lt;/span&gt;,
    ...
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we can set our &lt;code&gt;DATABASE_URL&lt;/code&gt; in the root &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongodb://root:password@localhost:27017/db-dev?replicaSet&lt;span class="o"&gt;=&lt;/span&gt;rs0&amp;amp;authSource&lt;span class="o"&gt;=&lt;/span&gt;admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;a href="http://localhost:27017" rel="noopener noreferrer"&gt;localhost:27017&lt;/a&gt; for the host because we exposed the container of the primary node on our machine on 127.0.0.1:27017.&lt;/p&gt;

&lt;p&gt;To test that everything works, let’s add some dummy models to our &lt;code&gt;schema.prisma&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;generator&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prisma-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;output&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../generated/prisma&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;datasource&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mongodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;        &lt;span class="nb"&gt;String&lt;/span&gt;   &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;id&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;     &lt;span class="nb"&gt;String&lt;/span&gt;   &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;unique&lt;/span&gt;
  &lt;span class="nx"&gt;posts&lt;/span&gt;     &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="nx"&gt;createdAt&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;updatedAt&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;updatedAt&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;        &lt;span class="nb"&gt;String&lt;/span&gt;   &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;id&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;     &lt;span class="nb"&gt;String&lt;/span&gt;
  &lt;span class="nx"&gt;slug&lt;/span&gt;      &lt;span class="nb"&gt;String&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;   &lt;span class="nb"&gt;String&lt;/span&gt;
  &lt;span class="nx"&gt;published&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;authorId&lt;/span&gt;  &lt;span class="nb"&gt;String&lt;/span&gt;   &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;
  &lt;span class="nx"&gt;createdAt&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;updatedAt&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;updatedAt&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nx"&gt;author&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;references&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that we can sync the schema with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma db push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which will generate the Prisma client and create the collections in MongoDB (we don’t use &lt;code&gt;migrate&lt;/code&gt; because it doesn’t support migrations like relational databases). And to have a visual of all those changes, we can run the Prisma studio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma studio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And open &lt;a href="http://localhost:5555" rel="noopener noreferrer"&gt;localhost:5555&lt;/a&gt; where our two collections should display, even if they are currently empty.&lt;/p&gt;

&lt;p&gt;Let’s instantiate the Prisma Client we will use in the application. I created a prisma.ts file in the lib folder of the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/prisma.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../generated/prisma/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;globalForPrisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;globalForPrisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;globalForPrisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We make sure to create one single instance of the Prisma Client by using a global variable. And a seed script to put some dummy data in the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/seed.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./prisma&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create a new user with some posts&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice@test.test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;createMany&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;First Post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;first-post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is my first post!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;His mother had always taught him&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;his-mother-had-always-taught-him&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;His mother had always taught him not to ever think of himself as better than others. He'd tried to live by this motto. He never looked down on those who were less fortunate or who had less money than him. But the stupidity of the group of people he was talking to made him change his mind.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;He was an expert but not in a discipline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;he-was-an-expert-but-not-in-a-discipline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;He was an expert but not in a discipline that anyone could fully appreciate. He knew how to hold the cone just right so that the soft server ice-cream fell into it at the precise angle to form a perfect cone each and every time. It had taken years to perfect and he could now do it without even putting any thought behind it.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dave watched as the forest burned up on the hill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dave-watched-as-the-forest-burned-up-on-the-hill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dave watched as the forest burned up on the hill, only a few miles from her house. The car had been hastily packed and Marta was inside trying to round up the last of the pets. Dave went through his mental list of the most important papers and documents that they couldn't leave behind. He scolded himself for not having prepared these better in advance and hoped that he had remembered everything that was needed. He continued to wait for Marta to appear with the pets, but she still was nowhere to be seen.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attach the seed script to the config in the prisma.config.ts (install &lt;a href="https://www.npmjs.com/package/tsx" rel="noopener noreferrer"&gt;tsx&lt;/a&gt; as a dev dependency)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prisma/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prisma/schema.prisma&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prisma/migrations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tsx src/lib/seed.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;classic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;datasource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma db seed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we should now seed those entries in the database through the Prisma Studio. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F35ermvc3etvelp9mi1gz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F35ermvc3etvelp9mi1gz.png" alt="Prisma Studio" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s fetch that data in our application to properly display it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Feomelfeccedrc3v68gtv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Feomelfeccedrc3v68gtv.png" alt="Posts list" width="708" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each post can be viewed individually:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fsi80wvvhhygrob1fre2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fsi80wvvhhygrob1fre2b.png" alt="individual post" width="708" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we can also create a new posts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fg7n6b6whh09kt5kt42bv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fg7n6b6whh09kt5kt42bv.png" alt="Create post" width="708" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which will appear&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkyd79g2t2951rmjt10r8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkyd79g2t2951rmjt10r8.png" alt="Created post added to the list of posts" width="708" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it. We were able to create our replica set locally using Docker Compose, and connect to it in our application with Prisma. This setup is ideal for development and testing or when you don’t want to use MongoDB Atlas. Thanks you for reading and happy coding !&lt;/p&gt;

</description>
      <category>prisma</category>
      <category>mongodb</category>
      <category>docker</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Introducing MoonCode: an application that monitors you coding activity, powered by a VS Code extension with offline support</title>
      <dc:creator>Friedrich WT</dc:creator>
      <pubDate>Tue, 17 Mar 2026 16:48:28 +0000</pubDate>
      <link>https://dev.to/friedrich482/introducing-mooncode-an-application-that-monitors-you-coding-activity-powered-by-a-vs-code-4040</link>
      <guid>https://dev.to/friedrich482/introducing-mooncode-an-application-that-monitors-you-coding-activity-powered-by-a-vs-code-4040</guid>
      <description>&lt;p&gt;During the last months, I have being building an application called MoonCode to track my coding time, languages and projects I was working on. And let me visualize all that on a modern looking dashboard. In this article, I want to write about it, the features it currently has, how it works, why I built it and what I learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Presentation
&lt;/h2&gt;

&lt;p&gt;MoonCode is a platform tracking and giving you a summary of your coding activity: coding time, languages, files, all in one single place. To use it, you just need to install the VS Code extension, login, then everything is handled for you. Currently the project only supports VS Code as code editor.&lt;/p&gt;

&lt;p&gt;Website link: &lt;a href="https://mooncode.cc" rel="noopener noreferrer"&gt;mooncode.cc&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  MoonCode’s Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;summary of your coding time, languages and projects per day, week, month, year, or any custom period&lt;/li&gt;
&lt;li&gt;support for most programming languages and files extensions. The extension automatically detects the language you are using&lt;/li&gt;
&lt;li&gt;the dashboard to visualize your data is local. It comes out of the box when you install the VS Code extension&lt;/li&gt;
&lt;li&gt;real-time sync dashboard (every minute). The stats always stay fresh and up to date with the data collected by the extension&lt;/li&gt;
&lt;li&gt;the extension works offline and synchronize to the API once you’re back online. So no data loss if you lose your internet connexion&lt;/li&gt;
&lt;li&gt;it also keeps your stats up to date automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some screenshots of the dashboard and the extension:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fk26lyu89j7t39u63hvhr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fk26lyu89j7t39u63hvhr.png" alt="MoonCode dashboard overview" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fswquo16fp3z0cvqc82kl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fswquo16fp3z0cvqc82kl.png" alt="MoonCode individual project overview" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3dvswpqrls7n0ykvxqec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3dvswpqrls7n0ykvxqec.png" alt="MoonCode profile page overview" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fy4lap5urmoa1zlnuhe01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fy4lap5urmoa1zlnuhe01.png" alt="MoonCode extension in work" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Link on the VS Code marketplace if you want to try it: &lt;a href="https://marketplace.visualstudio.com/items?itemName=Friedrich482.mooncode&amp;amp;ssr=false#overview" rel="noopener noreferrer"&gt;MoonCode VS Code extension&lt;/a&gt;. And the GitHub repository: &lt;a href="https://github.com/Friedrich482/mooncode" rel="noopener noreferrer"&gt;MoonCode&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The main parts of MoonCode are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;VS Code extension&lt;/strong&gt; that collect all the relevant metrics (files data and programming languages)&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;API&lt;/strong&gt; that manages all requests, built using &lt;strong&gt;NestJS&lt;/strong&gt;. This choice was made because of the modular architecture of the framework&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;Vite+React Router dashboard&lt;/strong&gt; that comes with the extension and allow you to visualize all the data in a user-friendly way&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;PostgreSQL database&lt;/strong&gt;: a battle tested and robust option for an SQL database, used to store all the data needed: user and analytics data in one single place&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;Turborepo monorepo&lt;/strong&gt; to keep all the parts and shared packages in a single, unified TypeScript codebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;trpc&lt;/strong&gt; between the API, the dashboard and the VS Code extension to allow end-to-end type-safety within the monorepo&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;website&lt;/strong&gt;, built using &lt;strong&gt;NextJS&lt;/strong&gt;. It is not really a core part, but more like the landing page of the project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, in on &lt;a href="https://excalidraw.com" rel="noopener noreferrer"&gt;excalidraw&lt;/a&gt; diagram it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwjh02sx2j97d5dqit6yb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwjh02sx2j97d5dqit6yb.png" alt="MoonCode architecture" width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I wanted to build it
&lt;/h2&gt;

&lt;p&gt;Why building a productivity coding time tracker when there are already plenty of them out there? Especially when there is already &lt;a href="https://wakatime.com" rel="noopener noreferrer"&gt;WakaTime&lt;/a&gt; doing the same thing, is more established, battle-tested and very feature-rich?&lt;/p&gt;

&lt;p&gt;I found the free-tier of Wakatime a little bit limiting (as a cheap developer 😏). Don’t get me wrong, it is an awesome project and product and my goal is not to discredit it. I just decided to build my own, that does exactly what I need and give me full control over the data. About the data control, more on that later.&lt;/p&gt;

&lt;p&gt;This project didn’t have to be feature complete, it didn’t have to be the perfect app, it just needed to solve that specific issue I had: track and monitor my coding statistics on any period I want.&lt;/p&gt;

&lt;p&gt;And honestly this seemed as a very exciting side-project to work on. Because not only I would build something that could solve a problem I had, but it was very exciting to me personally. I already knew to build frontend web apps, but didn’t know enough about backend but the most interesting part was definitely the extension. How would it work and its life-cycle in general. I decided to focus only on VS Code for the moment because it is the code editor I use on the day to day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned/lessons
&lt;/h2&gt;

&lt;p&gt;While building MoonCode I learned quite a few things about how VS Code extensions work, backend and APIs in general and Nest.js in particular. I also taught me some tips and tricks about TypeScript monorepos. I will try to be short here because some of the points deserve their own detailed blog post.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VS Code extensions&lt;/strong&gt;: they were intimidating for me at the beginning, especially because not too much is said about them. We usually build frontend apps and backend systems, not vscode plugins. But the extensions are just Node.js applications with an entrypoint that is the &lt;code&gt;extension.js&lt;/code&gt; (built from &lt;code&gt;extension.ts&lt;/code&gt;) file, and it can be changed anyways. They have a &lt;code&gt;package.json&lt;/code&gt; like any other Node.js app. The main difference with the other apps, is that you can use the VS Code API to create and execute VS Code commands, show notifications, subscribe to events happening in the code editor,… Two approaches are mostly used to build VS Code extensions: Functional Programming (FP) and Oriented Object Programming (OOP) approaches. I chose the FP approach since it is the one I was the most comfortable with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nest.js&lt;/strong&gt;: before building MoonCode, I didn’t do a lot of backend, I had mostly worked with Next.js and even though it offers some backend functionalities, it is not a full fleshed backend framework. In my case the API I will build would be consumed by the extension itself, the dashboard and maybe other applications/packages later. So a separate backend was necessary. As I said earlier, I chose Nest.js for it scalability and opinionated nature (in fact, I wanted to try something new that was stricter, stable and battle-tested so I could really learn the backend side of applications). And I can say without any doubt that it is an, awesome framework, everything is organised in modules and properly separated. Even though Nest.js uses Oriented Object Programming, I didn’t need to be a master at it to be productive. The mental model was a little bit odd at the beginning but I started to click as I was using it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript monorepos and trpc&lt;/strong&gt;: Turborepo is a simple yet very powerful tool to manage TypeScript monorepos, but it took me a very long time (many weeks/months) to really take advantages of its features: the cli, the cache,… I also used trpc to make everything typesafe from the backend to the frontend apps. It is so beautiful how the typesafety flows like water in the different applications. But it was very tricky to setup. I messed a lot with tsconfigs across the monorepo to get what I wanted. Some apps used ES Modules, others like Nest were still on CommonJS… It was a real chaos to make everybody share a typesafe contract, but it ended up being wired properly. What I also really like about monorepos is the ability to share common code/utility functions between all the apps in &lt;a href="https://turborepo.dev/docs/crafting-your-repository/creating-an-internal-package" rel="noopener noreferrer"&gt;packages&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker in monorepos&lt;/strong&gt;: this one is also tricky but I can just have one single docker compose file with all my apps. Each of them has its own dockerfile. Or (this one is my favorite approach) deploy each app individually using its Dockerfile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building MoonCode was a real journey, I learned a lot. Some days it got very tricky due to the nature of what I was building (a VS Code extension was something new, but working with time in programming is, well, very tricky). But one thing I would 100% avoid if I had to start again is over-engineering. I wasted so many hours, days and even weeks trying to perfect the folder structure of the apps, get the ESLint rules right, …. It was useful in some cases like the VS Code extension and otherwise I would have ended with a real mess. But some days I felt like it was more like an excuse to not continue building, a fake productivity.&lt;/p&gt;

&lt;p&gt;I still fall in that trap and I’m learning to detect when I start over-engineering and stop it if so. I have shipped the extension way earlier but I spent a lot of time fixing small issues, not getting anything really serious done for a long time. This is something I needed to hammer down in my head: avoid over-engineering at all costs. Perfection is the enemy, it kills momentum. Ship something that works. It doesn’t mean produce bad quality code, but just get it done. And move on with your day.&lt;/p&gt;

&lt;p&gt;About the data ownership, one of initial goals of this project was to make it in a way to allow the user(s) to self-host the API and the database. This can be accomplished with Docker for sure. And since the dashboard is bundled with the VS Code extension, it doesn’t have to be self-hosted. The pain point I hit was to wire the VS Code extension and the API, so the user could update the API URL in the VS Code extension to their self-hosted version. I might find a good solution and ship it soon.&lt;/p&gt;

&lt;p&gt;So, yes, that’s it. Thanks you if you made if so far in the article and happy coding !&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>react</category>
      <category>typescript</category>
      <category>nestjs</category>
    </item>
  </channel>
</rss>
