DEV Community

Cover image for Zero to Hero: Building a Full-Stack App with PropelAuth, Vite, Typescript, Express, and MongoDB
Richard Choi
Richard Choi

Posted on

Zero to Hero: Building a Full-Stack App with PropelAuth, Vite, Typescript, Express, and MongoDB

Have you ever built your Javascript full-stack applications and wanted to integrate seamless and secure authentication without setting up all the backend for the authentication logic? We’re going to build a full-stack application that does this using Propel Auth, a team-based authentication platform for B2B SaaS applications, with Vite as the framework, Typescript as the frontend, Express as the backend, and MongoDB as the database to store our data.

So why Propel Auth? Not only does Propel Auth allow a variety of authentication options like 2FA or SAML, it also allows you to manage your relationships with the created users, improving the overall user experience.

Setup Vite

We will need to set up the foundations of our application first, so use the command below and follow the wizard prompts for React and Typescript. Feel free to customize the project name, but for the sake of this example, we are going to call this project VTuberHub.

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

Make sure to install the packages to run your Vite app, and test it locally to make sure you set it up correctly. Now that we have our framework set up, we can start working on setting up our Database!

Setup MongoDB

If you don’t have an account, you can sign up for a free account at the MongoDB site. Create an organization and customize the name for your purposes, but we will be calling it My-Project for this example.

MongoDB Name Your Project with My-project in the input screenshot

Do the same for your project and its name.

MongoDB prices list and name of cluster screenshot

Deploy your new cluster, where your Database will be located. For the sake of this example, we will be calling it Vtubers.

PropelAuth dashboard my project as the name of the project screenshot

We are almost finished with setting up our database! Make sure to configure who has access to the Database under the Database Access sidebar navigation and set up a password for the user, we will need this for later.

Setup Propel Auth

If you haven't already, make sure to create a PropelAuth account. You'll want to create your project and give it a name. For this article, we'll be calling it my project.

PropelAuth frontend integration with http://localhost:5173 in Application URL input screenshot

We'll need to install propel auth to our project by using the following command:

npm install @propelauth/react
Enter fullscreen mode Exit fullscreen mode

Go to Frontend integration and enter http://localhost:5173 under the Application URL input box. This way, we can tell Propel Auth where our Vite application is running locally, allowing requests like POST or GET from our project.

Propel Auth frontend integration Application URL http://localhost:5173 int input screenshot

You'll need to copy and paste your Auth URL into your .env file so we can access it later.

VITE_AUTH_URL=https://something.propelauthtest.com
Enter fullscreen mode Exit fullscreen mode

Make sure to set up your main.tsx properly for Vite by wrapping our application in the AuthProvider component so we can fetch the current user's authentication information.

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { AuthProvider } from "@propelauth/react";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <AuthProvider authUrl={import.meta.env.VITE_AUTH_URL}>
      <App />
    </AuthProvider>
  </StrictMode>,
);
Enter fullscreen mode Exit fullscreen mode

Now we're all setup with our frontend, lets build a simple UI toggle depending on if the user is signed in or not!

import {
  withAuthInfo,
  useRedirectFunctions,
  useLogoutFunction,
  WithAuthInfoProps,
} from "@propelauth/react";

const YourApp = withAuthInfo((props: WithAuthInfoProps) => {

  const logoutFunction = useLogoutFunction();
  const { redirectToLoginPage, redirectToSignupPage, redirectToAccountPage } = useRedirectFunctions();

  if (props.isLoggedIn) {
    return (
      <div>
        <p>You are logged in as {props.user.email}</p>
        <button onClick={() => redirectToAccountPage()}>Account</button>
        <button onClick={() => logoutFunction(true)}>Logout</button>
      </div>
    );
  } else {
    return (
      <div>
        <p>You are not logged in</p>
        <button onClick={() => redirectToLoginPage()}>Login</button>
        <button onClick={() => redirectToSignupPage()}>Signup</button>
      </div>
    );
  }
});

export default YourApp;
Enter fullscreen mode Exit fullscreen mode

Looks good, now let's start working on our backend so we can add data to our database from our frontend!

Setup Backend

We’ll need to now set up our backend so we can handle our frontend requests to send data to our Database. Make sure to create a separate folder for your backend before initializing the express backend using the following command:

npm init -y
Enter fullscreen mode Exit fullscreen mode

We will need to install certain dependencies for our example application needs. Outside of Express and MongoDB, we will be using the dotenv package to prevent exposing our environment variables, and the cors package to enable our users to access our backend API requests.

 npm i cors dotenv express mongodb
Enter fullscreen mode Exit fullscreen mode

Once we install all our packages, we will need to import them into our backend code logic using the require() function. Make sure to use the cors function to enable its use by frontend users, the critical function express.json() for JSON data handling, and the express server hosting for the backend.

require('dotenv').config();
const express = require('express');
const app = express();
const cors = require('cors');

app.use(express.json());
app.use(cors());

// Start the server
app.listen(3000, () => {
  console.log('Server running on port 3000');
});
Enter fullscreen mode Exit fullscreen mode

Now we need to set up the connection from our backend to the MongoDB database we just created. Remember the password I mentioned in the MongoDB set up section?

We need it here for the MongoDB connection URL, which you can find in ClustersConnectDrivers → Copy the MongoDB connection string and replace the password with the one you created in the MongoDB setup section. This is the connection URL you will be using in your .env file to protect exposing it, and for the sake of this example, we will be calling it MONGO_URL. Make sure your .env file is in the same folder as your backend files so they can grab the MONGO_URL environment variable.

Connect your MongoDB client with the backend and make sure your backend is properly connecting to MongoDB before proceeding forward.

const { MongoClient, ServerApiVersion } = require("mongodb");
const mongoUrl = process.env.MONGO_URL

const client = new MongoClient(mongoUrl,  {
    serverApi: {
        version: ServerApiVersion.v1,
        strict: true,
        deprecationErrors: true,
    }
});

async function run() {
  try {
    // Connect the client to the server (optional starting in v4.7)
    await client.connect();
    // Send a ping to confirm a successful connection
    await client.db("admin").command({ ping: 1 });
    console.log("Pinged your deployment. You successfully connected to MongoDB!");
  } catch(error){
    res.send(`Error connecting to server, ${error}`)
  }
}

run().catch(console.dir);
Enter fullscreen mode Exit fullscreen mode

Now for our final step, we gotta let our backend accept CRUD requests so we can create documents in our Database.

const myDB = client.db("myDB");
const myColl = myDB.collection("users");

app.post("/createVtuber", async(req, res) => {
  try{
    const result = await myColl.insertOne(req.body);
    console.log(
       `A document was inserted with the _id: ${result.insertedId}`,
    );
  }catch(err){
    res.send(`error sending request, ${err}`)
  }

});
Enter fullscreen mode Exit fullscreen mode

Frontend

Time to finish up our Frontend, we still need to make the actual request to our backend and send our data! We’re going to install the Axios package to handle our API requests from the frontend.

 npm i axios
Enter fullscreen mode Exit fullscreen mode

Now we can create a function to make the POST request for our button!

import axios from "axios"

async function addDB(){
 await axios.post("http://localhost:3000/createVtuber", {name: "hatsune miku", colors: "blue"});
}

const YourApp = withAuthInfo((props: WithAuthInfoProps) => {
  const logoutFunction = useLogoutFunction();
  const { redirectToLoginPage, redirectToSignupPage, redirectToAccountPage } = useRedirectFunctions();

if (props.isLoggedIn) {
    return (
      <div>
        <p>You are logged in as {props.user.email}</p>
        <button onClick={() => redirectToAccountPage()}>Account</button>
        <button onClick={() => logoutFunction(true)}>Logout</button>
        <button onClick={() => addDB()}>Create Vtuber</button>
      </div>
    );
  } else {
    return (
      <div>
        <p>You are not logged in</p>
        <button onClick={() => redirectToLoginPage()}>Login</button>
        <button onClick={() => redirectToSignupPage()}>Signup</button>
      </div>
    );
}}
Enter fullscreen mode Exit fullscreen mode

Of course, our data wouldn’t be statically hard-coded like this, we would use state management to manage our data.

But we don’t want just anyone using our backend, right? So let’s make it so only authorized users are able to access our backend.

Adding authorized users

Going back to our backend folder, we’re going to install Propel Auth for express.

npm i @propelauth/express
Enter fullscreen mode Exit fullscreen mode

Make sure to grab your Propel Auth URL and API key, which you can find under Backend Integration in our Propel Auth project dashboard. We will be storing these values in our backend .env file as well, which for the sake of this example will be called AUTH_URL and SECRET_KEY.

We’ll need to import initAuth to initialize the authentication services in our backend and add our AUTH_URL and SECRET_KEY variables into the initAuth method.

const { initAuth } = require("@propelauth/express");

const {
    requireUser,
} = initAuth({
    authUrl: process.env.AUTH_URL,
    apiKey: process.env.SECRET_KEY, 
});

app.post("/createUser", requireUser, async (req, res) => {
  try {
    const result = await myColl.insertOne(req.body);
    console.log(`A document was inserted with the _id: ${result.insertedId}`);
  } catch (err) {
    res.send(`error sending request, ${err}`);
  }
});
Enter fullscreen mode Exit fullscreen mode

For our frontend, we just have to add the access token to our headers when we make our POST request, and if we’re signed in, we will have the proper authorization to make the request to our backend server.

  async function addDB() {
    const data = await axios.post(
      "http://localhost:3000/createUser",
      {
        name: "hatsune miku",
        colors: "blue",
      },
      {
        headers: {
          Authorization: `Bearer ${props.accessToken}`,
        },
      }
    );
  }
Enter fullscreen mode Exit fullscreen mode

So our frontend should look like this:

 async function addDB() {
    const data = await axios.post(
      "http://localhost:3000/createUser",
      {
        name: "hatsune miku",
        colors: "blue",
      },
      {
        headers: {
          Authorization: `Bearer ${props.accessToken}`,
        },
      }
    );
  }
Enter fullscreen mode Exit fullscreen mode

Use Tailwind/CSS to make the app look pretty, and voila! Our example full-stack application is finished, and we know how to integrate Propel Auth with Express, Vite, TypeScript, and MongoDB. For a deeper dive into Propel Auth and what it provides, look into their API Key Authentication, or how they manage role permission.

Top comments (0)