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
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.
Do the same for your project and its name.
Deploy your new cluster, where your Database will be located. For the sake of this example, we will be calling it Vtubers.
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.
We'll need to install propel auth to our project by using the following command:
npm install @propelauth/react
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.
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
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>,
);
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;
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
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
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');
});
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 Clusters → Connect → Drivers → 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);
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}`)
}
});
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
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>
);
}}
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
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}`);
}
});
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}`,
},
}
);
}
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}`,
},
}
);
}
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)