April 25, 2022

Build A Fullstack App with Remix, Prisma & MongoDB: Project Setup

9 min read

Welcome to the first article in this series, where you will take a look at how to build a full-stack application from the ground up using MongoDB, Prisma, and Remix! In this article, you will be setting up your project, the MongoDB instance, Prisma, and begin modeling out some of our data for the next section of this series.

Table Of Contents

Introduction

The goal of this series is to take an in-depth look at how to start, develop and deploy an application using the technologies mentioned below and hopefully highlight just how easy it is to do so with the rich feature sets these tools provide!

By the end of this series, you will have built and deployed an application called "Kudos", a site where users can create an account, log in, and give kudos to other users of the site. It will end up looking something like this:

Technologies we will use

Throughout this series you will be using the following tools to build this application:

  • MongoDB as the database
  • Prisma as your Object Document Mapper (ODM)
  • Remix as the React framework
  • TailwindCSS for styling the application
  • AWS S3 for storing user-uploaded images
  • Vercel for deploying the application

What this series covers

You will be diving into every aspect of building this application, including:

  • Database configuration
  • Data modeling
  • Authentication with session-based auth
  • Create, Read, Update and Delete (CRUD) operations, along with the filtering and sorting of data using Prisma
  • Image uploads using AWS S3
  • Deploying to Vercel

What you will learn today

In this first article, you will go through the process of starting up a Remix project, setting up a MongoDB database using Mongo's Atlas platform, installing Prisma, and beginning to model out some of the data for the next section of this series. By the end, you should have a strong foundation to continue building the rest of your application on.

Prerequisites

Assumed knowledge

While this series is meant to guide you through the development of a fullstack application, the following previous knowledge will be assumed:

  • Experience working in a JavaScript ecosystem
  • Experience with React, as Remix is a framework built on React
  • A basic understanding of "schemaless" database concepts, specifically with MongoDB
  • A basic understanding of working with Git

Development environment

In order to follow along with the examples provided, you will be expected to ...

Note: The optional extensions add some really nice intellisense and syntax highlighting for Tailwind and Prisma.

Generate the Remix application

The very first thing you will need to do is initialize a Remix application. Remix is a fullstack web framework that allows you to easily build entire React applications without having to worry about the application's infrastructure.

It helps you to focus on developing your applications rather than spending time managing multiple areas of the stack separately and orchestrating their interactions.

It also provides a nice set of tools you will make use of to aid in otherwise tedious tasks.

To start off a Remix project, run the following command in a location where you would like this project to live:

npx create-remix@latest kudos
Copy

This will scaffold a starter project for you and ask you a couple of questions. Choose the following options to let Remix know you want a blank project using TypeScript and you intend to deploy it to Vercel.

  • What type of app do you want to create? Just the basics
  • Where do you want to deploy? Choose Remix if you're unsure, it's easy to change deployment targets. Vercel
  • TypeScript or JavaScript? TypeScript
  • Do you want me to run npm install? Yes

Take a look at the starter project

Once the project is set up, go ahead and pop it open by either opening the project in your code editor or by running the command code . within that folder in your terminal if you are using VSCode's CLI.

You will see the generated boilerplate project with a file structure that looks like this:

│── app
│ ├── entry.client.tsx
│ ├── entry.server.tsx
│ ├── root.tsx
│ └── routes
│ ├── README.md
│ └── index.tsx
├── node_modules
├── package-lock.json
├── package.json
├── public
│ └── favicon.ico
├── remix.config.js
├── remix.env.d.ts
├── server.js
├── tsconfig.json
├── README.md
└── vercel.json

For the majority of this series, you will be working within the app directory, which will hold all of the custom code for this application.

Any file within ./app/routes will be turned into a route. For example, assuming your application is running on localhost:3000, the ./app/routes/index.tsx file will result in a generated route at localhost:3000/. If you were to create another file at app/routes/home.tsx, Remix would generate a localhost:3000/home route in your site.

This is one of the magical pieces of Remix that makes development so easy! Of course, there are a lot more powerful features along with this basic example. If you are curious, check out their docs on the routing capabilities.

Note: You can read more about Remix's routing here. You will also be using other routing features such as nested routes and resource routes later on in the series!

If you run this project with the command npm run dev and head over to http://localhost:3000/, you should see the basic starter application.

Great! Your basic project is started up and Remix has already scaffolded out many of the pieces you would normally have had to set up manually, such as the routing and build process. Now you will move on to setting up TailwindCSS so you can make the application look nice!

Set up TailwindCSS

TailwindCSS provides a robust set of utility classes and functions that will help you quickly build beautiful user interfaces that are easily customizable to fit your custom design needs. You will be making use of TailwindCSS for all of the styling in this application.

Tailwind has a great guide that goes through the steps of configuring it in a Remix project. You will be guided through those steps below:

To start things off, there are a few dependencies you will need in order to use Tailwind:

npm install -D tailwindcss postcss autoprefixer concurrently
Copy

This will install the following development dependencies:

  • tailwindcss: The command-line interface (CLI) that allows you to initialize a Tailwind configuration.
  • postcss: TailwindCSS is a PostCSS plugin and relies on PostCSS to be built.
  • autoprefixer: A PostCSS plugin used to add browser-specific prefixes to your generated CSS automatically. This is required by TailwindCSS.
  • concurrently: This allows you to run your Tailwind build process alongside the Remix build process.

Once those are installed, you can initialize Tailwind in the project:

npx tailwindcss init -p
Copy

This will generate two files:

  • tailwind.config.js: This is where you can tweak and extend TailwindCSS. See all of the options here.
  • postcss.config.js: PostCSS is a CSS transpiler. This file is where you can add plugins.

When a build is run, Tailwind will scan through the codebase to determine which of its utility classes it needs to bundle into its generated output. You will need to let Tailwind know which files it should look at to determine this. In tailwind.config.js, add the following glob pattern to the content key:

// tailwind.config.js
module.exports = {
content: [
+ "./app/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

This will tell Tailwind that any file inside of the app folder with the provided extensions should be scanned through for keywords and class names that Tailwind will pick up on to generate its output file.

Next, in package.json update your scripts section to include a build process for Tailwind when the application is built and when the development server runs. Add the following scripts:

// package.json
{
"scripts": {
"build": "npm run build:css && remix build",
"build:css": "tailwindcss -m -i ./styles/app.css -o app/styles/app.css",
"dev": "concurrently \"npm run dev:css\" \"remix dev\"",
"dev:css": "tailwindcss -w -i ./styles/app.css -o app/styles/app.css"
}
}
Copy

You may notice a few of the scripts are pointing to a file at ./styles/app.css that does not exist yet. This will be Tailwind's source file when it is built and where you will import the various functions and directives Tailwind will use.

Go ahead and create that source file at ./styles/app.css and add each of Tailwind's layers using the @tailwind directive:

/* ./styles/app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Copy

Now when the application is run or built, your scripts will also kick off the process to run Tailwind's scanning and building process. The result of this will be outputted into app/styles/app.css.

That file is what you will import into your Remix application to allow you to use Tailwind in your code!

In app/root.tsx, import the generated stylesheet and export a links function to let Remix know you have an asset you want to be imported into all of your modules when the application is built:

// ./app/root.tsx
// 1
import type { MetaFunction, LinksFunction } from "@remix-run/node";
// 2
import styles from './styles/app.css';
// ...
// 3
export const links: LinksFunction = () => {
return [{ rel: 'stylesheet', href: styles }]
}
// ...
Copy

The code above will:

  1. Import the type for Remix's links function.
  2. Import the generated stylesheet.
  3. Export a function named links, which follows a convention Remix picks up on and uses to import assets into all modules.

Note: If you had exported a links function within an individual route file rather than the root.tsx file, it would be load the assets returned on that route only. For more info on asset imports and conventions, check out Remix's docs.

Now go into the ./app/routes/index.tsx file and replace its contents with the following sample to make sure Tailwind is set up correctly:

// ./app/routes/index.tsx
export default function Index() {
return (
<div className="h-screen bg-slate-700 flex justify-center items-center">
<h2 className="text-blue-600 font-extrabold text-5xl">TailwindCSS Is Working!</h2>
</div>
)
}
Copy

You should see a screen that looks something like this:

Note: If you do not see Tailwind's styles being applied to your page, you may need to restart your development server.

If that looks good, you have successfully configured TailwindCSS and can move on to the next step, setting up the database!

Create a MongoDB instance

In this project, you will be using Prisma to interact with a MongoDB database. Before you configure Prisma, however, you will need a MongoDB instance to connect to!

You will set up a MongoDB cluster using Mongo's Atlas cloud data platform.

Note: You could, of course, set up a MongoDB instance any way you are comfortable. Atlas, however, provides the easiest and quickest experience. The only requirement by Prisma is that your MongoDB is deployed with a replica set.

Head over to the Atlas home page linked above. If you don't already have an account, you'll want to create one.

If you will be using an existing account, head to the dashboard. From there you will see a dropdown in the top left corner of the screen. If you pop that open you will see the option New Project.

Once you click on that, hit the Build a Database button.

From there you should be able to follow along with the rest of the steps below.

You should land on a screen with a few options. Choose the Free option for the purposes of this series. Then hit the Create button:

When you select that option, you will be brought to a page that allows you to configure the cluster that will be generated. For your application, you can use the default settings. Just click Create Cluster near the bottom right of the page.

This will kick off the provisioning and deployment of your MongoDB cluster! All you need now is a database user and a way to connect to the database. Fortunately, MongoDB will walk you through this setup during their quickstart process.

You will see a few prompts that help you make these configurations. Follow the prompts to create a new user.

Then, in the Where would you like to connect from? section, hit Add My Current IP Address to whitelist your development machine's IP address, allowing it to connect to the database.

With those steps completed, your database should finish its provisioning process within a few minutes (at most) and be ready for you to play with!

Set up Prisma

Now that you have a MongoDB database to connect to, it's time to set up Prisma!

Initialize and configure Prisma

The first thing you will want to do is install the Prisma CLI as a development dependency. This is what will allow you to run various Prisma commands.

npm i -D prisma
Copy

To initialize Prisma within the project, simply run:

npx prisma init --datasource-provider mongodb
Copy

This will create a few different files in your project. You will see a prisma folder with a schema.prisma file inside of it. This is where you will define your schema and model out your data.

It will also generate a .env file automatically if one did not previously exist with a sample environment variable that will hold your database's connection string.

If you open up ./prisma/schema.prisma you should see a default starter template of a Prisma schema.

// ./prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
Copy

Note: This file is written in PSL (Prisma Schema Language), which allows you to map out your schema. For more information on Prisma schemas and PSL, check out the Prisma docs.

In the url of the datasource block, you can see it references the DATABASE_URL environment variable from the .env file using the env() function PSL provides. Prisma uses dotenv under the hood to expose those variables to Prisma.

Set your environment variable

You will now give Prisma the correct connection string in your environment variable so it will be able to connect to the database.

To find your connection string on the Atlas dashboard hit the Connect button.

This will pop open a modal. Hit the Connect your application option.

This should reveal a few bits of information. The piece you care about is the connection string.

In your .env file, replace the default connection string with your MongoDB connection string. This connection string should follow this format:

mongodb+srv://USERNAME:PASSWORD@HOST:PORT/DATABASE
Copy

After pasting in your connection string and modifying it to match the above format, you should be left with a string that looks like this:

mongodb+srv://sadams:<password>@cluster0.vv1we.mongodb.net/kudos?retryWrites=true&w=majority
Copy

Note: Notice the kudos database name. You can put any name you want for your DATABASE here. MongoDB will automatically create the new database if it does not already exist.

For more details on connecting to your MongoDB database, check out the docs.

Model the data

Now you can begin to think about your data model and start to map out the collections for your database. Note that you are not going to model out your entire dataset in this article. Instead, you will iteratively build the Prisma schema throughout the entire series.

For this section, however, create a User model you will use in the next section of this series which handles setting up authentication.

Over in prisma/prisma.schema, add a new model to your schema named User. This will be where you define what a user should look like in the database.

// ./prisma/schema.prisma
model User {
}
Copy

Note: MongoDB is a schemaless database built for flexible data so it may seem counterintuitive to define a "schema" for the data you are storing in it. As schemaless databases grow and evolve, however, the problem occurs where it becomes difficult to keep track of what data lives where while accounting for legacy data shapes. Because of this, defining a schema may save some headaches in the long run.

Every Prisma model needs to have a unique id field.

// ./prisma/schema.prisma
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
}
Copy

The code above will create an id field and let Prisma know this is a unique identifier with the @id attribute. Because MongoDB automatically creates an _id field for every collection, you will let Prisma know using the @map attribute that while you are calling this field id in the schema, it should map to the _id field in the database.

The code will also define the data type for your id field and set a default value of auto(), which will allow you to make use of MongoDB's automatically generated unique IDs.

Note: When using Prisma with MongoDB, every model must have a unique identifier field defined exactly like this to properly map to the _id field MongoDB generates. The only part of this field definition that may vary in your schema is what you decide to name the field you map to the underlying _id field.

Now that you have an id field, go ahead and add some other useful data to the User model.

// ./prisma/schema.prisma
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String @unique
password String
}
Copy

As you can see above, you will be adding two DateTime type fields that will keep track of when a user gets created and when it is updated. The @updatedAt attribute will automatically update that field with a current timestamp any time that user is updated.

It will also add an email field of type String that must be unique, indicated by the @unique attribute. This means no other user can have the same email.

Finally, you will have a password field which is just a plain string.

That's all you will need in the User model for now! You can now push this schema to MongoDB so you can see the collection it creates.

Push schema changes

After making changes to our schema you can run the command:

npx prisma db push
Copy

This will push your schema changes to MongoDB, creating any new collections or indexes you have defined. For example, when you push your schema as it is now, you should see the following in the output:

Applying the following changes:
[+] Collection `User`
[+] Unique index `User_email_key` on ({"email":1})

Because MongoDB is schemaless, there is no real concept of migrations. A schemaless database's data can fluidly change and evolve as the application's scope grows and changes. This command simply creates the defined collections and indexes.

Summary & What's next

In this article, you got your Remix application up and running, along with your MongoDB instance. You also set up Prisma and TailwindCSS in your project and began to model out the data you will use in the next section of this series.

In the next article you will learn about:

  • Setting up session-based authentication in Remix
  • Storing and modifying user data with Prisma and MongoDB
  • Building a Login form
  • Building a Signup form

Join the discussion

Follow @prisma on Twitter

Don’t miss the next post!

Sign up for the Prisma newsletter