• Latest
  • Trending
Fullstack Authentication with Remix using Prisma, MongoDB and Typescript

Fullstack Authentication with Remix using Prisma, MongoDB and Typescript

May 16, 2022
Absa and Visa Extend Strategic Partnership to Advance Growth and Innovation Across Africa

Absa and Visa Extend Strategic Partnership to Advance Growth and Innovation Across Africa

July 29, 2025
French Telco Orange Hit by Cyber-Attack

French Telco Orange Hit by Cyber-Attack

July 29, 2025
ATC Ghana supports Girls-In-ICT Program

ATC Ghana supports Girls-In-ICT Program

April 25, 2023
Vice President Dr. Bawumia inaugurates  ICT Hub

Vice President Dr. Bawumia inaugurates ICT Hub

April 2, 2023
Co-Creation Hub’s edtech accelerator puts $15M towards African startups

Co-Creation Hub’s edtech accelerator puts $15M towards African startups

February 20, 2023
Data Leak Hits Thousands of NHS Workers

Data Leak Hits Thousands of NHS Workers

February 20, 2023
EU Cybersecurity Agency Warns Against Chinese APTs

EU Cybersecurity Agency Warns Against Chinese APTs

February 20, 2023
How Your Storage System Will Still Be Viable in 5 Years’ Time?

How Your Storage System Will Still Be Viable in 5 Years’ Time?

February 20, 2023
The Broken Promises From Cybersecurity Vendors

Cloud Infrastructure Used By WIP26 For Espionage Attacks on Telcos

February 20, 2023
Instagram and Facebook to get paid-for verification

Instagram and Facebook to get paid-for verification

February 20, 2023
YouTube CEO Susan Wojcicki steps down after nine years

YouTube CEO Susan Wojcicki steps down after nine years

February 20, 2023
Inaugural AfCFTA Conference on Women and Youth in Trade

Inaugural AfCFTA Conference on Women and Youth in Trade

September 6, 2022
  • Consumer Watch
  • Kids Page
  • Directory
  • Events
  • Reviews
Thursday, 16 July, 2026
  • Login
itechnewsonline.com
  • Home
  • Tech
  • Africa Tech
  • InfoSEC
  • Data Science
  • Data Storage
  • Business
  • Opinion
Subscription
Advertise
No Result
View All Result
itechnewsonline.com
No Result
View All Result

Fullstack Authentication with Remix using Prisma, MongoDB and Typescript

by ITECHNEWS
May 16, 2022
in Data Science, Leading Stories
0 0
0
Fullstack Authentication with Remix using Prisma, MongoDB and Typescript

Remix is an edge-first server-side rendered JavaScript framework built on React that allows us to build full-stack web applications thanks to its frontend and server-side capabilities. With the motto “Web Fundamentals, Modern UX” as its APIs follow as much as possible the web standards like: HTTP responses, form submissions, inbuilt loader for data fetching and many exciting features baked in.

In the recent 2021 ‘Javascript Rising Stars’ Remix was ranked among the top full stack framework of choice among developers. Remix got a lot of traction (and $3M in seed funding, which helps too!) and it was open sourced. But, Remix is not a new framework as previously it was available as a subscription-based premium framework.

YOU MAY ALSO LIKE

French Telco Orange Hit by Cyber-Attack

ATC Ghana supports Girls-In-ICT Program

What are we building

We will make the use of Remix alongside with MongoDB as our database with Prisma ORM using Typescript and build a fully working authentication application from scratch. For this we will make the use of ‘Built-in Support for Cookies’ feature provided as a built-in function called createCookie to work with cookies.

Prerequisites

  • Node.js 14+ (this uses v16.14.0)
  • npm 7+
  • A code editor

Creating the project

We will first initialize a new Remix project with the command

npx create-remix@latest

We will give a name to our project and call it

remix-mongo-auth

We also want to start with just the basic starter template and proceed with the rest of installation process. We have also used Tailwind to spice up our application the starter files can be found in the repository here.

Connecting our database

For our database we are using MongoDB which is a non-relational document based database. For our ease we will configure it using Mongo Atlas and grab the connection string from there to later configure our app.

Please note you may need to activate the admin rights your user in operating some tasks later. It can be done under database access settings.

Configuring PrismaORM

We will start with installing the Prisma dev dependency in order to interact with the MongoDB and push the database changes.

npm i -D prisma

This will install the Prisma CLI for us. We then want to initialize prisma using the MongoDB (default to Postgres) with the command

npx prisma init --datasource-provider mongodb

We must now see a prisma folder created into our directory and inside that will be our schema.prisma file created for us. Inside the file we will write prisma schema launguage where we will create models needed to do the authentication implementation.

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

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

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

type Profile {
  fullName String
}

Here we created a user model and profile model. A user will have its reference to the Profile document.

The id column is a string which is an auto generated values provided by Mongo. @db.ObjectId is to give any unique id to the database. DateTime @default(now()) is the current timestamp we have provided to createdAt. Rest columns is just a data type we provide to the data structure.

In order to see and reflect the changes inside our database we need to add a new file which will be responsible in connecting our database and Remix application.

//utils/prisma.server.ts
import { PrismaClient } from "@prisma/client";
let prisma: PrismaClient;
declare global {
  var __db: PrismaClient | undefined;
}

if (process.env.NODE_ENV === "production") {
  prisma = new PrismaClient();
  prisma.$connect();
} else {
  if (!global.__db) {
    global.__db = new PrismaClient();
    global.__db.$connect(); 
  }
  prisma = global.__db;
}

export * from "@prisma/client";
export { prisma };

The above snippet is taken from Remix document where it will instantiate new PrismaClient if no existing connection client to DB is found.

Now, we can run the command to apply schema changes.

npx prisma db push   

This will create any new collection and indexes defined in our schema. We can now check if our changes are all working. We can run command

npx prisma studio      

This will spin up a default port, where we can see the reflection of changes with the columns that is created to us. Which will look something as below

Image description

Adding a Layouts

We want our application to have a standard layout in which we can wrap all the application inside it. This comes in handy if we will create multiple Layouts in multiple pages, passing a children prop.

export function Layout({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>;
}

Registering Users

Let’s start adding the registration for new users. We will need to install some libraries before we begin. We will need a library lets install it

npm i bcrypt

This library will help us in hashing our password before we save it in our database. As we really dont want to act fool saving plain text passwords in our DB. To learn more about hashing useing bcrypt please refer this article here.

Creating type interface

As we are using typescript we will first begin by creating the type interface for our Registration data types needed. Below is the type we created

//utils/types.server.ts
export type RegisterForm = {
  email: string;
  password: string;
  fullName?: string;
};

We will now create a function which will take in the user object which contains our email, password and full name and turn that password to the hashed password, finally creates a new user in our MongoDB.

Note: Adding .server extension will let Remix know it is the server file which wont get bundled in our client-side javascript file. So sensitive information wont get exposed to clients.

//utils/user.server.ts
import bcrypt from "bcryptjs";
import type { RegisterForm } from "./types.server";
import { prisma } from "./prisma.server";

export const createUser = async (user: RegisterForm) => {
  const passwordHash = await bcrypt.hash(user.password, 10);
  const newUser = await prisma.user.create({
    data: {
      email: user.email,
      password: passwordHash,
      profile: {
        fullName: user.fullName,
      },
    },
  });
  return { id: newUser.id, email: user.email };
}; 

We will now make the use of Cookie feature provided by Remix. Which helps us to generate new cookie session.

//utils/auth.server.ts
export async function createUserSession(userId: string, redirectTo: string) {
  const session = await storage.getSession();
  session.set("userId", userId);
  return redirect(redirectTo, {
    headers: {
      "Set-Cookie": await storage.commitSession(session),
    },
  });
}

Until this point we have created our createCookieSessionStorage function which will create a new cookie session. Lets create this function

//utils/auth.server.ts

const sessionSecret = process.env.SESSION_SECRET;
if (!sessionSecret) throw new Error("Secret not specified, it must be set");

const storage = createCookieSessionStorage({
  cookie: {
    name: "remix-mongo-auth",
    secure: process.env.NODE_ENV === "production",
    secrets: [sessionSecret],
    sameSite: "lax",
    path: "/",
    maxAge: 60 * 60 * 24 * 30,
    httpOnly: true,
  },
});

Now we have everything needed to write our registerUser function. Which will check the user exist in database with a unique email. If there is unique email we will create a new user session if not we will send a JSON response with something went wrong.

//utils/auth.server.ts
export async function registerUser(form: RegisterForm) {
  const userExists = await prisma.user.count({ where: { email: form.email } });
  if (userExists) {
    return json(
      { error: `User already exists with that email` },
      { status: 400 }
    );
  }

  const newUser = await createUser(form);
  if (!newUser) {
    return json(
      {
        error: `Something went wrong trying to create a new user.`,
        fields: { email: form.email, password: form.password, fullName: form.fullName },
      },
      { status: 400 }
    );
  }
  return createUserSession(newUser.id, "/");
}
//utils/auth.server.ts

export async function getUser(request: Request) {
  const userId = await getUserId(request);
  if (typeof userId !== "string") {
    return null;
  }

  try {
    const user = await prisma.user.findUnique({
      where: { id: userId },
      select: { id: true, email: true, profile: true },
    });
    return user;
  } catch {
    throw logout(request);
  }
}

function getUserSession(request: Request) {
  return storage.getSession(request.headers.get("Cookie"));
}

export async function requireUserId(
  request: Request,
  redirectTo: string = new URL(request.url).pathname
) {
  const session = await getUserSession(request);
  const userId = session.get("userId");
  if (!userId || typeof userId !== "string") {
    const searchParams = new URLSearchParams([["redirectTo", redirectTo]]);
    throw redirect(`/auth/login?${searchParams.toString()}`);
  }
  return userId;
}

We will create one additional function which will return us the user info of the user that has been created to us.

//utils/user.server.ts
async function getUserId(request: Request) {
  const session = await getUserSession(request);
  const userId = session.get("userId");
  if (!userId || typeof userId !== "string") return null;
  return userId;
}

export async function getUser(request: Request) {
  const userId = await getUserId(request);
  if (typeof userId !== "string") {
    return null;
  }

  try {
    const user = await prisma.user.findUnique({
      where: { id: userId },
      select: { id: true, email: true, profile: true },
    });
    return user;
  } catch {
    throw logout(request);
  }
}

After everything needed to create a new user function is written. We will create couple of new files inside our routes folder.

//routes/index.ts
import { LoaderFunction, redirect } from '@remix-run/node';
import { requireUserId } from '~/utils/auth.server';

export const loader: LoaderFunction = async ({ request }) => {
  await requireUserId(request);
  return redirect('/home');
};

Inside our main index.ts file we will check is we do have the user id available to us if it results to true we will redirect to /home route.

//routes/auth/register.tsx
import { useState } from 'react';
import { Layout } from '~/layout/layout';
import { Link, useActionData } from '@remix-run/react';
import { ActionFunction, LoaderFunction, redirect } from '@remix-run/node';
import { registerUser, getUser } from '~/utils/auth.server';

export const loader: LoaderFunction = async ({ request }) => {
  // If user has active session, redirect to the homepage
  return (await getUser(request)) ? redirect('/') : null;
};

export const action: ActionFunction = async ({ request }) => {
  const form = await request.formData();
  const email = form.get('email');
  const password = form.get('password');
  const fullName = form.get('fullName');

  if (!email || !password || !fullName) {
    return {
      status: 400,
      body: 'Please provide email and password',
    };
  }

  if (
    typeof email !== 'string' ||
    typeof password !== 'string' ||
    typeof fullName !== 'string'
  ) {
    throw new Error(`Form not submitted correctly.`);
  }

  const allFields = { email, password, fullName };
  const user = await registerUser(allFields);
  return user;
};

export default function Register() {
  const actionData = useActionData();
  const [formError, setFormError] = useState(actionData?.error || '');

  return (
    <>
      <Layout>
        <div className="min-h-full flex items-center justify-center mt-[30vh]">
          <div className="max-w-md w-full space-y-8">
            <div>
              <span className="text-center text-slate-400 block">
                Welcome fellas!
              </span>
              <h2 className="text-center text-3xl font-extrabold text-gray-900">
                Register your account
              </h2>
            </div>

            <form method="post">
              <div>
                <div>
                  <label htmlFor="email-address" className="sr-only">
                    Full name
                  </label>
                  <input
                    id="user-name"
                    name="fullName"
                    type="text"
                    autoComplete="name"
                    required
                    className="appearance-none rounded-none relative block w-full px-3 py-4 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
                    placeholder="Full name"
                    defaultValue={actionData?.fullName}
                  />
                </div>
                <div>
                  <label htmlFor="email-address" className="sr-only">
                    Email address
                  </label>
                  <input
                    id="email-address"
                    name="email"
                    type="email"
                    autoComplete="email"
                    required
                    className="appearance-none rounded-none relative block w-full px-3 py-4 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
                    placeholder="Email address"
                    defaultValue={actionData?.email}
                  />
                </div>
                <div>
                  <label htmlFor="password" className="sr-only">
                    Password
                  </label>
                  <input
                    id="password"
                    name="password"
                    type="password"
                    autoComplete="current-password"
                    required
                    className="appearance-none rounded-none relative block w-full px-3 py-4 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
                    placeholder="Password"
                    defaultValue={actionData?.password}
                  />
                </div>
              </div>
              <button
                type="submit"
                className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 mt-5"
              >
                Register account
              </button>
              <div>
                <p className="text-sm text-center mt-5">
                  Already have an account?
                  <span className="underline pl-1 text-green-500">
                    <Link to="/auth/login">Login</Link>
                  </span>
                </p>
              </div>
              <div className="text-xs font-semibold text-center tracking-wide text-red-500 w-full">
                {formError}
              </div>
            </form>
          </div>
        </div>
      </Layout>
    </>
  );
}

Login Users

Let’s also create a function which will login new users into our application.

export async function loginUser({ email, password }: LoginForm) {
  const user = await prisma.user.findUnique({
    where: { email },
  });

  if (!user || !(await bcrypt.compare(password, user.password))) {
    return json({ error: `Incorrect login` }, { status: 400 });
  }

  //redirect to homepage if user created
  return createUserSession(user.id, '/');
}

This function will query our database and look for the email we have passed in as a parameter is there is no email and password don’t match we redirect to main route.

Adding Routing

It’s time we now can create all the route needed in our overall application. We will create couple of routes so that we can add some protected route and redirect when we don’t have cookie set. Routing inside Remix works the same like they would working with Next or Nuxt(SSR) applications.

Register route

//routes/auth/register.tsx
import { useState } from 'react';
import { Layout } from '~/layout/layout';
import { Link, useActionData } from '@remix-run/react';
import { ActionFunction, LoaderFunction, redirect } from '@remix-run/node';
import { registerUser, getUser } from '~/utils/auth.server';

export const loader: LoaderFunction = async ({ request }) => {
  // If user has active session, redirect to the homepage
  return (await getUser(request)) ? redirect('/') : null;
};

export const action: ActionFunction = async ({ request }) => {
  const form = await request.formData();
  const email = form.get('email');
  const password = form.get('password');
  const fullName = form.get('fullName');

  if (!email || !password || !fullName) {
    return {
      status: 400,
      body: 'Please provide email and password',
    };
  }

  if (
    typeof email !== 'string' ||
    typeof password !== 'string' ||
    typeof fullName !== 'string'
  ) {
    throw new Error(`Form not submitted correctly.`);
  }

  const allFields = { email, password, fullName };
  const user = await registerUser(allFields);
  return user;
};

export default function Register() {
  const actionData = useActionData();
  const [formError, setFormError] = useState(actionData?.error || '');

  return (
    <>
      <Layout>
        <div className="min-h-full flex items-center justify-center mt-[30vh]">
          <div className="max-w-md w-full space-y-8">
            <div>
              <span className="text-center text-slate-400 block">
                Welcome fellas!
              </span>
              <h2 className="text-center text-3xl font-extrabold text-gray-900">
                Register your account
              </h2>
            </div>

            <form method="post">
              <div>
                <div>
                  <label htmlFor="email-address" className="sr-only">
                    Full name
                  </label>
                  <input
                    id="user-name"
                    name="fullName"
                    type="text"
                    autoComplete="name"
                    required
                    className="appearance-none rounded-none relative block w-full px-3 py-4 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
                    placeholder="Full name"
                    defaultValue={actionData?.fullName}
                  />
                </div>
                <div>
                  <label htmlFor="email-address" className="sr-only">
                    Email address
                  </label>
                  <input
                    id="email-address"
                    name="email"
                    type="email"
                    autoComplete="email"
                    required
                    className="appearance-none rounded-none relative block w-full px-3 py-4 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
                    placeholder="Email address"
                    defaultValue={actionData?.email}
                  />
                </div>
                <div>
                  <label htmlFor="password" className="sr-only">
                    Password
                  </label>
                  <input
                    id="password"
                    name="password"
                    type="password"
                    autoComplete="current-password"
                    required
                    className="appearance-none rounded-none relative block w-full px-3 py-4 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
                    placeholder="Password"
                    defaultValue={actionData?.password}
                  />
                </div>
              </div>
              <button
                type="submit"
                className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 mt-5"
              >
                Register account
              </button>
              <div>
                <p className="text-sm text-center mt-5">
                  Already have an account?
                  <span className="underline pl-1 text-green-500">
                    <Link to="/auth/login">Login</Link>
                  </span>
                </p>
              </div>
              <div className="text-xs font-semibold text-center tracking-wide text-red-500 w-full">
                {formError}
              </div>
            </form>
          </div>
        </div>
      </Layout>
    </>
  );
}

Login route

import { useState } from 'react';
import { Layout } from '~/layout/layout';
import { useActionData, Link } from '@remix-run/react';
import { ActionFunction, LoaderFunction, redirect } from '@remix-run/node';
import { loginUser, getUser } from '~/utils/auth.server';

export const loader: LoaderFunction = async ({ request }) => {
  // If user has active session, redirect to the homepage
  return (await getUser(request)) ? redirect('/') : null;
};

export const action: ActionFunction = async ({ request }) => {
  const form = await request.formData();
  const email = form.get('email')?.toString();
  const password = form.get('password')?.toString();

  if (!email || !password)
    return {
      status: 400,
      body: 'Please provide email and password',
    };

  const user = await loginUser({ email, password });
  return user;
};

export default function Login() {
  const actionData = useActionData();
  const [formError, setFormError] = useState(actionData?.error || '');

  return (
    <>
      <Layout>
        <div className="min-h-full flex items-center justify-center mt-[30vh]">
          <div className="max-w-md w-full space-y-8">
            <div>
              <span className="text-center text-slate-400 block">
                Welcome back!
              </span>
              <h2 className="text-center text-3xl font-extrabold text-gray-900">
                Log in to your account
              </h2>
            </div>
            <form className="mt-8 space-y-6" action="#" method="POST">
              <input type="hidden" name="remember" value="true" />
              <div className="rounded-md shadow-sm -space-y-px">
                <div>
                  <label htmlFor="email-address" className="sr-only">
                    Email address
                  </label>
                  <input
                    id="email-address"
                    name="email"
                    type="email"
                    autoComplete="email"
                    required
                    className="appearance-none rounded-none relative block w-full px-3 py-4 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
                    placeholder="Email address"
                    defaultValue={actionData?.email}
                  />
                </div>
                <div>
                  <label htmlFor="password" className="sr-only">
                    Password
                  </label>
                  <input
                    id="password"
                    name="password"
                    type="password"
                    autoComplete="current-password"
                    required
                    className="appearance-none rounded-none relative block w-full px-3 py-4 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 sm:text-sm"
                    placeholder="Password"
                    defaultValue={actionData?.password}
                  />
                </div>
              </div>

              <div>
                <button
                  type="submit"
                  className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
                >
                  Log in
                </button>
              </div>
              <div>
                <p className="text-sm text-center">
                  I dont have an account?
                  <span className="underline pl-1 text-green-500">
                    <Link to="/auth/register">Register</Link>
                  </span>
                </p>
              </div>
              <div className="text-xs font-semibold text-center tracking-wide text-red-500 w-full">
                {formError}
              </div>
            </form>
          </div>
        </div>
      </Layout>
    </>
  );
}

Util this point we are ready to test our our implementation of session storage for our users. This should work as expected creating a new session for logged in users and also new session for fresh registered user.

Logged in page

We will create a logged in page where users can see their currently logged in username and email with a warm welcome message.

//routes/home.tsx
import {
  ActionFunction,
  LoaderFunction,
  redirect,
  json,
} from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { getUser } from '~/utils/auth.server';
import { logout } from '~/utils/auth.server';
import { Layout } from '~/layout/layout';

export const loader: LoaderFunction = async ({ request }) => {
  // If user has active session, redirect to the homepage
  const userSession = await getUser(request);
  if (userSession === null || undefined) return redirect('/auth/login');
  return json({ userSession });
};

export const action: ActionFunction = async ({ request }) => {
  return logout(request);
};

export default function Index() {
  const { userSession } = useLoaderData();
  const userName = userSession?.profile.fullName;
  const userEmail = userSession?.email;

  return (
    <>
      <Layout>
        <div className="text-center m-[30vh] block">
          <div>
            <small className="text-slate-400 pb-5 block">You are Logged!</small>
            <h1 className="text-4xl text-green-600 font-bold pb-3">
              Welcome to Remix Application
            </h1>
            <p className="text-slate-400">
              Name: {userName}, Email: {userEmail}
            </p>
          </div>
          <div className="text-sm mt-[40px]">
            <form action="/auth/logout" method="POST">
              <button
                name="_action"
                value="delete"
                className="font-medium text-red-600 hover:text-red-500"
              >
                Log me out
              </button>
            </form>
          </div>
        </div>
      </Layout>
    </>
  );
}

Logout Users

//routes/auth/logout.tsx
export async function logout(request: Request) {
  const session = await getUserSession(request); 
  return redirect("/auth/logout", {
    headers: {
      "Set-Cookie": await storage.destroySession(session),
    },
  });
}

We have made the use of storage.destroy method Remix has provided us to remove the session stored into our browsers. We also need to create a dedicated file which will redirect us to that route and remove session stored.

//route/auth/logout.tsx
import type { ActionFunction, LoaderFunction } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { logout } from "~/utils/auth.server";

export const action: ActionFunction = async ({ request }) => logout(request);
export const loader: LoaderFunction = async () => redirect("/");

Conclusion

We have successfully created our authentication with Remix, MongoDB, Prisma, Tailwind with Typescript. Although Remix is a fresh still growing framework there are lot of contributors working together to grow this framework.

Source: Ishan Manandhar
Via: dev.to
Tags: Fullstack Authentication with Remix using PrismaMongoDB and Typescript
ShareTweet

Get real time update about this post categories directly on your device, subscribe now.

Unsubscribe

Search

No Result
View All Result

Recent News

Absa and Visa Extend Strategic Partnership to Advance Growth and Innovation Across Africa

Absa and Visa Extend Strategic Partnership to Advance Growth and Innovation Across Africa

July 29, 2025
French Telco Orange Hit by Cyber-Attack

French Telco Orange Hit by Cyber-Attack

July 29, 2025
ATC Ghana supports Girls-In-ICT Program

ATC Ghana supports Girls-In-ICT Program

April 25, 2023

About What We Do

itechnewsonline.com

We bring you the best Premium Tech News.

Recent News With Image

Absa and Visa Extend Strategic Partnership to Advance Growth and Innovation Across Africa

Absa and Visa Extend Strategic Partnership to Advance Growth and Innovation Across Africa

July 29, 2025
French Telco Orange Hit by Cyber-Attack

French Telco Orange Hit by Cyber-Attack

July 29, 2025

Recent News

  • Absa and Visa Extend Strategic Partnership to Advance Growth and Innovation Across Africa July 29, 2025
  • French Telco Orange Hit by Cyber-Attack July 29, 2025
  • ATC Ghana supports Girls-In-ICT Program April 25, 2023
  • Vice President Dr. Bawumia inaugurates ICT Hub April 2, 2023
  • Home
  • InfoSec
  • Opinion
  • Africa Tech
  • Data Storage

© Copyright 2026, All Rights Reserved | iTechNewsOnline.Com - Powered by BackUPDataSystems

Welcome Back!

Login to your account below

Forgotten Password?

Retrieve your password

Please enter your username or email address to reset your password.

Log In

Add New Playlist

No Result
View All Result
  • Home
  • Tech
  • Africa Tech
  • InfoSEC
  • Data Science
  • Data Storage
  • Business
  • Opinion

© Copyright 2026, All Rights Reserved | iTechNewsOnline.Com - Powered by BackUPDataSystems

Are you sure want to unlock this post?
Unlock left : 0
Are you sure want to cancel subscription?
Go to mobile version