Lang
Blog

Welcome to Remix — A Newly Developed Full- stack Framework

ByPayal Mittal
December 7th . 5 min read
Welcome to Remix

A brand-new React framework has come into the IT hub — Remix.run. It’s a wonderful framework with exclusive and next-gen features.

There are many React frameworks but Remix stands out, which is not surprising considering its powerful functionalities.

Remix can write both the backend and the frontend code in the same file. With minimal JavaScript, we can now render data on the server and to the client.

Unlike vanilla React, where we fetch data on the frontend, which is then rendered on the screen, Remix fetches data on the backend and serves the HTML to the user directly. This guide will give you an overview of Remix and show you how to get started with this powerful framework -

Overview About Remix.run

Remix.run is a newly developed, open-source and a wonderful full-stack web development framework designed to help you quickly build web applications with a minimum of hassle.

With Remix, you can easily create complex user interfaces with a user-friendly syntax. The framework is designed to be modular and extensible, so you can pick and choose the components you need for your project and create custom applications.

In short -

  • It includes everything you need to get started, from already configured stacks, to a simple, easy-to-use server to a robust routing system and powerful templates.
  • No separation of concerns
  • While you might be confused about it being a frontend framework, it’s NOT. You can write the frontend as well the backend code in the same files.
  • Easy to deploy your applications to the web, with support for a variety of hosting providers.
  • With its simple code, you’ll be up and running in no time.

It’s wise to say that Remix is a complete pleasure to work with and is on the path of becoming one of the greatest frameworks.

The framework is still in its early stages, but it has already seen some success, with a number of high-profile projects using Remix.run to power their websites.

How to Get Started?

The first thing you need to make sure before trying Remix is to check whether you have all the prerequisites fulfilled, i.e.-

  • Node.js installed version > 14.17
  • npm and a code editor
npx create-remix@latest --template remix-run/indie-stack blog-tutorial

You can find the detailed guide to create your own Remix app is given here, which if you follow step wise, you will see how easy it is. The most wonderful thing is stacks which you can use to start quickly with your app without having to create it from the scratch.

Also, a stack comes with a complete signup and login pages with UI and authentication.

Now, let’s check out the code of my first remix app, which is a simple blog-tutorial app, which i created with the above command. It shows some simple real-time examples of how routing works in remix.

Honestly, I followed the step-wise guide from the docs and created some pages through routing and nested routing. But the amazing thing was that the data was coming from Prisma, which is one of the greatest strengths of TypeScript.

Check out the result here-

Remix-1.gif

Now, let’s check out the code- The code structure is same as given in the developer’s guide to Remix.

Here’s my app/routes.index.tsx file for the home page-

import { Link } from "@remix-run/react";
import { useOptionalUser } from "~/utils";
export default function Index() {
  const user = useOptionalUser();
  return (
    <main className="relative min-h-screen bg-white sm:flex sm:items-center sm:justify-center">
      <div className="relative sm:pb-16 sm:pt-8">
        <div className="mx-auto max-w-7xl sm:px-6 lg:px-8">
          <div className="relative shadow-xl sm:overflow-hidden sm:rounded-2xl">
            <div className="relative px-4 pt-16 pb-8 sm:px-6 sm:pt-24 sm:pb-14 lg:px-8 lg:pb-20 lg:pt-32">
              <h1 className="text-center text-5xl font-extrabold tracking-tight sm:text-4xl lg:text-9xl">
                <span className="block uppercase text-yellow-500 drop-shadow-md">
                  Indie Stack
                </span>
              </h1>
              <div className="mx-auto mt-16 max-w-7xl text-center">
                <Link
                  to="/posts"
                  className="text-xl text-blue-600 underline"
                >
                  Click here to check Blog Posts
                </Link>
              </div>
              <div className="mx-auto mt-10 max-w-sm sm:flex sm:max-w-none sm:justify-center">
                {user ? (
                  <Link
                    to="/notes"
                    className="flex items-center justify-center rounded-md border border-transparent bg-white px-4 py-3 text-base font-medium text-yellow-700 shadow-sm hover:bg-yellow-50 sm:px-8"
                  >
                    View Notes for {user.email}
                  </Link>
                ) : (
                  <div className="space-y-4 sm:mx-auto sm:inline-grid sm:grid-cols-2 sm:gap-5 sm:space-y-0">
                    <Link
                      to="/join"
                      className="flex items-center justify-center rounded-md border border-transparent bg-white px-4 py-3 text-base font-medium text-yellow-700 shadow-sm hover:bg-yellow-50 sm:px-8"
                    >
                      Sign up
                    </Link>
                    <Link
                      to="/login"
                      className="flex items-center justify-center rounded-md bg-yellow-500 px-4 py-3 font-medium text-white hover:bg-yellow-600"
                    >
                      Log In
                    </Link>
                  </div>
                )}
              </div>
              <a href="https://remix.run">
                <img
                  src="https://user-images.githubusercontent.com/1500684/158298926-e45dafff-3544-4b69-96d6-d3bcc33fc76a.svg"
                  alt="Remix"
                  className="mx-auto mt-16 w-full max-w-[12rem] md:max-w-[16rem]"
                />
              </a>
            </div>
          </div>
        </div>
      </div>
    </main>
  );
}

This is the app/models/post.server.ts file where we define our post models-

import { prisma } from "~/db.server";
import type { Post } from "@prisma/client";
export type { Post };
export async function getPosts() {
  return prisma.post.findMany();
}
export async function getPost(slug: string) {
    return prisma.post.findUnique({ where: { slug } });
  }
  export async function createPost(
    post: Pick<Post, "slug" | "title" | "markdown">
  ) {
    return prisma.post.create({ data: post });
  }
}

Here’s the app/routes/posts/index.tsx file for the /posts route page-

import { json } from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react";
import { getPosts } from "~/models/post.server";
type LoaderData = {
    // this is a handy way to say: "posts is whatever type getPosts resolves to"
    posts: Awaited<ReturnType<typeof getPosts>>;
};
export const loader = async () => {
    return json<LoaderData>({
        posts: await getPosts(),
    });
};
export default function Posts() {
    const { posts } = useLoaderData() as LoaderData;
    return (
        <main>
            <div className="absolute inset-0">
                <div className="absolute inset-0 bg-[color:rgba(00,204,27,0.5)] mix-blend-multiply" />
            </div>
            <div className="relative px-4 pt-16 pb-8 sm:px-6 sm:pb-14 lg:px-8 lg:pb-20">
                <h1 className="text-center pb-8 text-3xl font-bold underline tracking-tight sm:text-6xl ">Posts</h1>
                <Link to="admin" className="text-red-600 underline">
                    Admin
                </Link>
                <ul>
                    {posts.map((post) => (
                        <li key={post.slug}>
                            <Link
                                to={post.slug}
                                className="flex text-violet-600 underline"
                            >
                                {post.title}
                            </Link>
                        </li>
                    ))}
                </ul>
            </div>
        </main>
    );
}

In the above file where we are getting the posts data, we have used the exported loader function which contains the loaded posts through useLoaderData hook. This is an excellent example that shows how easily remix combines the frontend and the backend.

The prisma schema file, i.e. prisma/schema.prisma looks like this-

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

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

model User {
  id    String @id @default(cuid())
  email String @unique
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  password Password?
  notes    Note[]
}
model Password {
  hash String
  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
  userId String @unique
}
model Note {
  id    String @id @default(cuid())
  title String
  body  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
  userId String
}
model Post {
  slug     String @id
  title    String
  markdown String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

This is the seed.ts file for prisma-

import { PrismaClient } from "@prisma/client";
import bcrypt from "bcryptjs";
const prisma = new PrismaClient();

async function seed() {
  const email = "rachel@remix.run";
  // cleanup the existing database
  await prisma.user.delete({ where: { email } }).catch(() => {
    // no worries if it doesn't exist yet
  });
  const hashedPassword = await bcrypt.hash("racheliscool", 10);
  const user = await prisma.user.create({
    data: {
      email,
      password: {
        create: {
          hash: hashedPassword,
        },
      },
    },
  });
  await prisma.note.create({
    data: {
      title: "My first note",
      body: "Hello, world!",
      userId: user.id,
    },
  });
  await prisma.note.create({
    data: {
      title: "My second note",
      body: "Hello, world!",
      userId: user.id,
    },
  });
  const posts = [
  {
    slug: "my-first-post",
    title: "My First Post",
    markdown: `
# This is my first post
Isn't it great?
    `.trim(),
  },
  {
    slug: "90s-mixtape",
    title: "A Mixtape I Made Just For You",
    markdown: `
# 90s Mixtape
- I wish (Skee-Lo)
- This Is How We Do It (Montell Jordan)
- Everlong (Foo Fighters)
- Ms. Jackson (Outkast)
- Interstate Love Song (Stone Temple Pilots)
- Killing Me Softly With His Song (Fugees, Ms. Lauryn Hill)
- Just a Friend (Biz Markie)
- The Man Who Sold The World (Nirvana)
- Semi-Charmed Life (Third Eye Blind)
- ...Baby One More Time (Britney Spears)
- Better Man (Pearl Jam)
- It's All Coming Back to Me Now (Céline Dion)
- This Kiss (Faith Hill)
- Fly Away (Lenny Kravits)
- Scar Tissue (Red Hot Chili Peppers)
- Santa Monica (Everclear)
- C'mon N' Ride it (Quad City DJ's)
    `.trim(),
  },
];
for (const post of posts) {
  await prisma.post.upsert({
    where: { slug: post.slug },
    update: post,
    create: post,
  });
}
  console.log(`Database has been seeded. 🌱`);
}
seed()
  .catch((e) => {
    console.error(e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

Here goes app/routes/posts/$slug.tsx file to get post slugs-

import { marked } from "marked";
import type { LoaderFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import invariant from "tiny-invariant";
import type { Post } from "~/models/post.server";
import { getPost } from "~/models/post.server";
type LoaderData = { post: Post; html: string };
export const loader: LoaderFunction = async ({
  params,
}) => {
  invariant(params.slug, `params.slug is required`);
  const post = await getPost(params.slug);
  invariant(post, `Post not found: ${params.slug}`);
  const html = marked(post.markdown);
  return json<LoaderData>({ post, html });
};
export default function PostSlug() {
  const { post, html } = useLoaderData() as unknown as LoaderData;
  return (
    <main className="mx-auto max-w-4xl">
      <h1 className="my-6 border-b-2 text-center text-3xl">
        {post.title}
      </h1>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </main>
  );
}

Here is the app/routes/posts/admin.tsx file for our /admin page-

import type { LoaderFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import {
  Link,
  Outlet,
  useLoaderData,
} from "@remix-run/react";
import { getPosts } from "~/models/post.server";
type LoaderData = {
  posts: Awaited<ReturnType<typeof getPosts>>;
};
export const loader: LoaderFunction = async () => {
  return json({ posts: await getPosts() });
};
export default function PostAdmin() {
  const { posts } = useLoaderData() as LoaderData;
  return (
    <div className="mx-auto max-w-4xl">
      <h1 className="my-6 mb-2 border-b-2 text-center text-3xl">
        Blog Admin
      </h1>
      <div className="grid grid-cols-4 gap-6">
        <nav className="col-span-4 md:col-span-1">
          <ul>
            {posts.map((post) => (
              <li key={post.slug}>
                <Link
                  to={post.slug}
                  className="text-blue-600 underline"
                >
                  {post.title}
                </Link>
              </li>
            ))}
          </ul>
        </nav>
        <main className="col-span-4 md:col-span-3">
          <Outlet />
        </main>
      </div>
    </div>
  );
}

Check the app/routes/posts/admin/index.tsx file where we link to /new route -

import { Link } from "@remix-run/react";
export default function AdminIndex() {
  return (
    <p>
      <Link to="new" className="text-blue-600 underline">
        Create a New Post
      </Link>
    </p>
  );
}

Finally, the file app/routes/posts/admin/new.tsx, which is a nested route component, where we create a form, validate it and use it to create a new post -

import { Form, useActionData, useTransition } from "@remix-run/react";
import type { ActionFunction } from "@remix-run/node";
import { redirect, json } from "@remix-run/server-runtime";
import invariant from "tiny-invariant";
import { createPost } from "~/models/post.server";
const inputClassName = `w-full rounded border border-gray-500 px-2 py-1 text-lg`;
type ActionData =
    | {
        title: null | string;
        slug: null | string;
        markdown: null | string;
    }
    | undefined;
export const action: ActionFunction = async ({
    request,
}) => {
    const formData = await request.formData();
    const title = formData.get("title");
    const slug = formData.get("slug");
    const markdown = formData.get("markdown");
    const errors: ActionData = {
        title: title ? null : "Title is required",
        slug: slug ? null : "Slug is required",
        markdown: markdown ? null : "Markdown is required",
    };
    const hasErrors = Object.values(errors).some(
        (errorMessage) => errorMessage
    );
    if (hasErrors) {
        return json<ActionData>(errors);
    }
    invariant(
        typeof title === "string",
        "title must be a string"
    );
    invariant(
        typeof slug === "string",
        "slug must be a string"
    );
    invariant(
        typeof markdown === "string",
        "markdown must be a string"
    );
    await createPost({ title, slug, markdown });
    return redirect("/posts/admin");
};
export default function NewPost() {
    const errors = useActionData();
    const transition = useTransition();
    const isCreating = Boolean(transition.submission);
    return (
        <Form method="post">
            <p>
                <label>
                    Post Title:{" "}
                    {errors?.title ? (
                        <em className="text-red-600">{errors.title}</em>
                    ) : null}
                    <input
                        type="text"
                        name="title"
                        className={inputClassName}
                    />
                </label>
            </p>
            <p>
                <label>
                    Post Slug:{" "}
                    {errors?.slug ? (
                        <em className="text-red-600">{errors.slug}</em>
                    ) : null}
                    <input
                        type="text"
                        name="slug"
                        className={inputClassName}
                    />
                </label>
            </p>
            <p>
                <label htmlFor="markdown">Markdown:{""}
                    {errors?.markdown ? (
                        <em className="text-red-600">
                            {errors.markdown}
                        </em>
                    ) : null}
                </label>
                <br />
                <textarea
                    id="markdown"
                    rows={20}
                    name="markdown"
                    className={`${inputClassName} font-mono`}
                />
            </p>
            <p className="text-right">
                <button
                    type="submit"
                    className="rounded bg-blue-500 py-2 px-4 text-white hover:bg-blue-600 focus:bg-blue-400 disabled:bg-blue-300"
                    disabled={isCreating}
                >
                    {isCreating ? "Creating..." : "Create Post"}
                </button>
            </p>
        </Form>
    );
}

Yes, you can create a form using <Form> tag, which is same as the <form> tag in HTML, and understand the form submission. Additionally, the action function here is responsible for getting and processing the form data.

And, that’s it folks! We did it. We created our first app with Remix, you can also update your UI through tailwind CSS.

What’s different about Remix.run?

Now, let’s catch up on some amazing things that Remix has introduced:

- Remix Stacks

A stack is a container that holds all the necessary components to run an application, which makes them much smaller and faster. Remix.run comes with several built-in and official stacks that are in itself an application, including -

  • Database
  • Automatic deployment pipelines
  • Authentication
  • Testing
  • Linting/Formatting/TypeScript

In short, everything from the backend to the frontend, database and required dependencies.

This way, you can just choose a stack and get started with your project quickly, enhancing the code in your own way and that too, without having to care about the setup and configuration.

Amazing right?

Here are the built-in stacks that Remix provides -

  • The Blues Stack: Deployment with Node.js server and PostgreSQL database. Designed for large, fast production-grade applications.
  • The Indie Stack: Deployed on Node.js server with SQLite database. It ideal for websites that contain dynamic data.
  • The Grunge Stack: Deployed on Node.js with DynamoDB. A great choice if you want to deploy a production-grade application on AWS infrastructure. Other interesting feature is that you can also create your own custom stacks in Remix to power up your application in terms of flexibility and power. If you’re looking to run an application on Remix.run, chances are you’ll need to use a stack.

- Routing in Remix.run

One of the key features of Remix.run is its routing system, which allows you to define the structure of your URLs and makes it easy to map URLs to specific resources in your application.

In Remix.run, routes can be nested within other routes, using params, to create more complex applications. Nested routing allows you to break up your application into smaller, more manageable pieces. It also makes it easier to reuse code and keep your application organized.

To create a nested route, you simply need to add a new route object within an existing route. The new route will then be accessible at the path specified by its parent route.

For example, if you have a route named /users and you add a nested route named /profile, the /profile route will be accessible at /users/profile. In the code examples, the /new is the nested route within parent /admin route, as we can see here.

When we click on the ‘Create a New Post’ link as given here-

Remix_2.webp

It opens up the route within the existing one, like this-

Remix_3.webp

- Easier Integration of frontend and backend

As we saw above, the loader function eliminates the need of creating APIs to call data through getPosts controller, or creating backend controller to handle the GET API, and other files for routing and managing the code.

It’s a huge development in Remix, which is yet to get noticed by the world. There’s no separate files for frontend and backend code, you can write both in a single file, which is completely opposite to the concept of ‘Separation of concerns’.

Though this was already happening with the introduction of JSX in React, but Remix is taking it to a whole other level.

You are writing the whole page in a single file along with its css and database request, which is a big step that brings a sense of unity of frontend and backend within the code and eases the code flow.

Conclusion

Besides all the wonderful things we learned here about Remix, the one that still stands out is that somehow, among working together with the backend as well as the frontend code, Remix has made development easier and NOT complicated.

Once you get the concept right, it’s plain and simple HTML.

Share:
0
+0