The Bee Hive

David Larrabee

David Larrabee
Technology Leader | Boston | @squidpunch

A simple CRUD app in Remix

Friday, June 25, 2021


Today is the first post of a multi part series I hope to show building the same simple CRUD app across a few javascript frameworks with a final post contrasting the experience.

Today we dive in using Remix.

Rough App Idea

A simple app that has the ability to Create Read Update Delete a central storage of web links. Essentially your bookmark bar, but with ability to track extra notes on why that link is important to have a reference.


You should always be thinking right tool, for the right job - we're just scratching the surface here but clearly just a markdown file locally to reference would suffice for what we are building today, but this gives us a small enough thing to bite off and compare against other frameworks to compare and contrast.

Let's get Started

I'll be sharing the repo and demo app to see the code and final results rather than calling out the next few steps. Ideally they are pretty much the same in each app, and the reason I am using standard tools that can be shared to compare.

A rough outline of the project though is shaped like this:

  • Project Setup: Initialize the project with the framework and dependencies
  • ORM Setup: Setup the models in the ORM
  • Pages and API: Create pages and API to interact with the ORM
  • Reaction: Some initial reactions

Project Setup


Installing Remix is a pretty smooth process. Just make sure you have a license and they've got a nice Tutorial to get you going

Linting / Formatting

Every project I generally drop in Prettier and Eslint to keep consistent with formatting and catching common formatting based mistakes

Database connectivity

For this app we will store information to a database, we'll leverage Prisma as our ORM to simplify that interactivity.

Styling Design System

Throwing in chakra-ui as a quick styling tool so I can test against next, gatsby, create react app - so using the same dependencies in each should level the playing field.

ORM Setup

This is simple enough, we're going to have just one model to work with, following the prisma documentation and prompts and we are off to the races, for context here is our schema file

generator client {
  provider = "prisma-client-js"

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")

model WebLinks {
  id       Int    @id @default(autoincrement())
  name     String @unique
  link     String
  notes    String
  category String

View on Github

Pages and API

Viewing Remix has you structure your app around routes. If you were going to have a bunch functionality maybe we would put our list view in something like app/routes/links/index.tsx but we'll just use the root file that was created from the generator for simplicty (app/routes/index.tsx)

As you see in the example pages have the ability to export a LoaderFunction which runs in the server processing of the request. Here we can pull the list of links through our prisma client and send it to the page. I'd say this is comparable to getServerSideProps from Next.js.

An interesting twist here is Remix can have javascript completely disabled, and this will still work. As you can turn off hydration and it will just server render the content and pass it along without any JS bundle at all. I believe this is possible in Next.js too but for those that are trying to ship less javascript. This is an appealing option to keep me building in react, but removing the bundle impact on our consumers. Without forcing it fully down the statically pre-built generated path.


Being routing first, you can create urls that really explain what we're doing easily. So we're going to create a way to create a new entry for our WebLinks model. All it takes to make this a valid path is to create a file. It can be or weblinks/new whichever grouping your team prefers. Simply export a default function with your rendered content, and you've got a route up and running.

Our page quickly styled using Chakra

import {
} from "@chakra-ui/react";
import type { ReactElement } from "react";

export default function WebLinkNew(): ReactElement {
  return (
    <Container maxW={"6xl"} mt={10}>
        <Heading pb={2}>Create a new Link</Heading>
      <form method="post">
            <Input placeholder="Name" required type="text" name="name" />
            <Input placeholder="Url" required type="text" name="link" />
              placeholder="Notes about the link"
              <Button variant="ghost" colorScheme="blue" as="a" href="/">
              <Button colorScheme="blue" type="submit">
                Create New Link

You'll notice we've got a form there but its not posting to an API or anything like you'd expect. Submittig the form though you'll see some helpful information letting us know how we can act on the submission

You made a POST request to http://localhost:3000/webLinks/new but did not provide an action for route "routes/", so there is no way to handle the request.

In other frameworks you'd probably spin up an API endpoint and post the data there, remix gives you the ability to handle the post in the same file that you are building the form by exporting an ActionFunction. This starting to make me feel closer to my Ruby on Rails roots, but with the view also in the mix.

After adding our ActionFunction, we've quickly ramped up with the ability to add records to our system and we can easily see the view and api side in one place.

import { PrismaClient } from "@prisma/client";
import { ActionFunction, Link, redirect } from "remix";
import {
} from "@chakra-ui/react";
import type { ReactElement } from "react";

export const action: ActionFunction = async ({ request }) => {
  const body = new URLSearchParams(await request.text());
  const name = body.get("name") as string;
  const category = body.get("category") as string;
  const link = body.get("link") as string;
  const notes = body.get("notes") as string;

  const prisma = new PrismaClient();

  async function main() {
    await prisma.webLinks.create({
      data: {

  await main()
    .catch((e) => {
      throw e;
    .finally(async () => {
      await prisma.$disconnect();

  return redirect("/");

(original render function)

View on Github

I'll throw in Editing and Deleting soon enough in the demo app, but this gives you a quick of the "C" and "R" of our "CRUD" app.



  • Using the React ecoystem to build a product that results in no JS bundle is an interesting value prop. In practice I wonder where this is the right fit - but it definitely helps pressure test should you include ALL that javascript, just to toggle a button?
  • I'm looking for what makes this different than Next.js, at first glance they seem to offer a lot of the same functionality with just some different patterns. That's the some of the original driver of writing this article.


Remix is still in Beta - so its expected to be a little tricky, I hit a few rough edges, but have worked through them. So far there are two that stick out the most to me.

yarn vs npm

More than once I would get mixed up depencies after running yarn install. So much so that I figured I would go back to npm like most of the doc examples from remix are written in. Since the switch I haven't hit one issue with installing dependencies getting linting errors or failures, so seems like yarn is less supported (at this time). This could totally be just my setup, but I noticed the problem pretty consistently when adding packages - my original assumption is because of the alternative package distribution for key checking etc.

Mismatching some HTML can easily crash your server :(

Being more familiar with Create React App and Next.js I believe the server is generally a bit more forgiving with my fat finger ways. Quite a few times I ended up here, and wondering why my app wasnt updating.

> remix-app-template@ dev /Users/davidlarrabee/workspace/playground/remix-web-link-portal
> remix run

Watching Remix app in development mode...
Remix App Server started at http://localhost:3000
💿 Built in 103ms
💿 File changed: app/routes/index.tsx
💿 Rebuilding...
 > route-module:/Users/davidlarrabee/workspace/playground/remix-web-link-portal/app/routes/index.tsx:44:6: error: Expected closing tag "div" to match opening tag "h2"
    44 │     </div>
       ╵       ~~~
   route-module:/Users/davidlarrabee/workspace/playground/remix-web-link-portal/app/routes/index.tsx:28:7: note: The opening tag "h2" is here
    28 │       <h2>Important Links
       ╵        ~~

 > route-module:/Users/davidlarrabee/workspace/playground/remix-web-link-portal/app/routes/index.tsx:47:0: error: Unexpected end of file
    47 │
       ╵ ^


npm ERR! errno 1
npm ERR! remix-app-template@ dev: `remix run`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the remix-app-template@ dev script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/davidlarrabee/.npm/_logs/2021-06-23T16_23_50_732Z-debug.log

Not something that I am surprised about, being so early on - but just calling it out there for the faint at heart.

What's next?

I'd love to dive in more with Remix -just have to come up with some other interesting challenges or ideas to chase. Ideally I'll write future posts comparing this same sample app in some alternatives like Next.js, Gatsby, and just straight up Create React App. I'll keep you posted!