Building a Full-Stack Quote Wall Application with Remix and User Authentication
Setting up a Quote Wall App with Remix
To get started, we need to scaffold a basic Remix application. We will choose “Just the basics” and “Remix App Server” when prompted, and then select TypeScript as our language.
npx create-remix@latest my-quote-wall-app
Next, we will add Tailwind CSS to our Remix app by installing the required packages and configuring our template paths.
npm install -D tailwindcss postcss autoprefixer concurrently
Setting up the Database
For data persistence, we will use a SQLite database with Prisma. We will install the required packages and initialize Prisma with SQLite.
npm install prisma @prisma/client
npx prisma init
We will then update our schema.prisma
file to define our database schema.
model Quote {
id String @id @default(cuid())
text String
author String
createdAt DateTime @default(now())
}
model User {
id String @id @default(cuid())
username String @unique
password String
}
User Authentication with Remix
In this section, we will implement user authentication using Remix’s built-in authentication features.
We will create an auth
directory and add a login.ts
file to handle login functionality.
// auth/login.ts
import { redirect } from '@remix-run/node';
import { Form, useTransition } from '@remix-run/react';
import { Authenticator } from '~/services/auth.server';
export async function action({ request }) {
const formData = await request.formData();
const username = formData.get('username');
const password = formData.get('password');
const user = await Authenticator.login(username, password);
if (user) {
return redirect('/quotes');
}
return null;
}
export default function Login() {
const transition = useTransition();
return (
Login
{transition.state === ‘ubmitting’? (
Logging in…
) : (
Login failed!
)}
);
}
We will also add a register.ts
file to handle user registration.
// auth/register.ts
import { redirect } from '@remix-run/node';
import { Form, useTransition } from '@remix-run/react';
import { Authenticator } from '~/services/auth.server';
export async function action({ request }) {
const formData = await request.formData();
const username = formData.get('username');
const password = formData.get('password');
const user = await Authenticator.register(username, password);
if (user) {
return redirect('/quotes');
}
return null;
}
export default function Register() {
const transition = useTransition();
return (
Register
{transition.state === ‘ubmitting’? (
Registering…
) : (
Registration failed!
)}
);
}
Quote Wall App Features
Now that we have user authentication set up, we can implement the quote wall app features.
Authenticated users will be able to view and publish quotes, while unauthenticated users will only be able to view the posts.
// routes/quotes.tsx
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { db } from '~/db.server';
export const loader = async () => {
const quotes = await db.quote.findMany();
return json({ quotes });
};
export default function Quotes() {
const { quotes } = useLoaderData();
return (
Quotes
-
{quotes.map((quote) => (
- {quote.text} – {quote.author}
))}
);
}
We will also add a form to allow authenticated users to publish new quotes.
// routes/quotes/new.tsx
import { redirect } from '@remix-run/node';
import { Form, useTransition } from '@remix-run/react';
import { db } from '~/db.server';
export async function action({ request }) {
const formData = await request.formData();
const text = formData.get('text');
const author = formData.get('author');
const quote = await db.quote.create({ data: { text, author } });
return redirect('/quotes');
}
export default function NewQuote() {
const transition = useTransition();
return (
New Quote
{transition.state === ‘ubmitting’? (
Publishing…
) : (
Quote published!
)}
);
}
Finally, we will add a navigation menu to allow users to navigate between the quote wall and the login/register pages.
// components/Nav.tsx
import { Link } from '@remix-run/react';
export default function Nav() {
return (
);
}