Building a Login/Register Application with React, Node.js, and Postgres | Beginner to Advanced

CodeGenitor
20 min readApr 26, 2024

--

Illustration representing the login/register process in the application.

Introduction:

In today’s digital world, user authentication is a crucial aspect of web development. Whether you’re building a simple blog or a complex web application, implementing a secure login system is essential to protect user data and ensure a seamless user experience. In this tutorial, we’ll walk through the process of building a login application using three powerful technologies: React for the frontend, Node.js for the backend, Typescript for JavaScript with static typing, improving code safety and maintainability, and PostgreSQL for the database from beginner to advanced level.

Overview of Technologies:

Before we dive into the implementation, it is always important to understand the underlined concepts before a deeper dive. Let’s briefly discuss each technology’s role in our application:

  • React: React offers a robust framework for dynamic web UIs. Its component-based design and virtual DOM streamline complex UI creation, enhancing code quality, scalability, and performance.
  • Node.js: enables efficient server-side JavaScript development, leveraging its non-blocking I/O model and vast ecosystem for scalable, real-time applications, streamlining development and facilitating modern web solutions.
  • PostgreSQL: is a robust open-source relational database. Offering ACID compliance, extensibility, and advanced features like JSONB support, it ensures reliable data storage and management for diverse application requirements.
  • Typescript: enhances JavaScript with static typing, improving code safety and maintainability. Features like type inference, interfaces, and generics aid in writing cleaner, more scalable code. It compiles JavaScript, offering better tooling support and developer productivity.
  • Eslint: a static code analysis tool for identifying and fixing problems in JavaScript code. It enforces coding standards and best practices, improving code quality and maintainability.
  • Formik: form management library for React that simplifies form state handling, validation, and submission.
  • Bootstrap: CSS framework that provides pre-designed components and styles for building responsive and visually appealing web applications.

1. Setting up the Environment:

Before we can start coding our application, we need to set up our development environment. Follow these steps:

Install Node.js and npm (Node Package Manager) on your system.
- On Mac Os as of this posting

# installs NVM (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# download and install Node.js
nvm install 21
# verifies the right Node.js version is in the environment
node -v # should print `v21.7.3`
# verifies the right NPM version is in the environment
npm -v # should print `10.5.0`

Or on windows

# download and install Node.js
choco install nodejs --version="21.7.3"
# verifies the right Node.js version is in the environment
node -v # should print `v21.7.3`
# verifies the right NPM version is in the environment
npm -v # should print `10.5.0`

2. Create a new React project using the Create React App.
As of this posting, you can use Nextjs with Reactjs if you visit the reactjs documentation at:

For this project, we’ll be using plain ReactJS.

I. Open a Terminal or Command Prompt:
Ensure you have Node.js installed, then navigate to your desired project directory — assuming you have a project directory called Projects.

cd projects; mkdir loginapp; cd loginapp; mkdir frontend backend;

II. Create Your React App:
Navigate to the frontend folder. Run the following command in your terminal:

cd frontend; npx create-react-app --template typescript .

You’re now ready to start working on your React project in typescript.

III. Add Eslint to the project by running the following command:

npm i -g eslint 
npx eslint --init

Follow the prompt to set up linting for your project:

You can also run this command directly using 'npm init @eslint/config'.
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ What format do you want your config file to be in? · JSON
Local ESLint installation not found.
The config that you've selected requires the following dependencies:
@typescript-eslint/eslint-plugin@latest eslint-plugin-react@latest @typescript-eslint/parser@latest eslint@latest
✔ Would you like to install them now? · No / Yes
? Which package manager do you want to use? …
npm
❯ yarn
pnpm4. Create UI components for the login/register form:

Now you should be able to add your rules to your project in the .eslintrc.json file

{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "react"],
"rules": {
"no-console": "warn",
"indent": ["error", 2],
"quotes": ["error", "double"],
"semi": ["error", "always"]
}
}

Note:
Ensure you’ve selected all the options that match your project’s needs and requirements, as demonstrated above. You should be able to set your rule to match your project needs as demonstrated above by editing the .eslintrc.json file. You should do the same for the backend.

3. Initializing the Nodejs project for the backend.

I. Open Terminal or Command Prompt: Launch your terminal or command prompt to initialise your backend server

cd projects; cd loginapp; cd backend; npm init -y 

II. Let's create a requirement to list all our dependencies:
Listing dependencies ensures effective management, reproducibility, and security of the project. It aids in tracking external libraries, version control, and mitigating security risks by documenting all required dependencies and their versions.

touch requirements.txt

III. Add the following libraries to your requirements.txt:
for simplicity, we will use the latest instead of listing all versions for the libraries

express@latest
@types/express@latest
nodemon@latest
chalk@3.0
dotenv@latest
@types/dotenv@latest
jsonwebtoken@latest
@types/jsonwebtoken@latest
cors@latest
@types/cors@latest
morgan@latest
@types/morgan@latest
pg
@types/pg
bcryptjs
@types/bcryptjs

Note:
remember you don’t need to list all the libraries on the go but whenever you will need a new library you just need to update your requirement.txt file and re-run installation as it comes in the next parts:

IV: Adding typescript to the nodejs backend server and initializing typescript with the following command.

npm i typescript ts-node @types/node --save-dev; npx tsc --init

Now you should have a tsconfig.json as above. Replace its content with the following:

{
"compilerOptions": {
"module": "commonjs",
"noImplicitReturns": true,
"noUnusedLocals": true,
"outDir": "lib",
"sourceMap": true,
"strict": true,
"target": "es2017",
"esModuleInterop": true
},
"compileOnSave": true,
"include": ["src/**/*"],
"exclude": ["node_modules", "lib"]
}

V. Install the dependencies:
ensure that you are in the terminal for the backend and run the following command:

npm i $(cat requirements.txt )

Here’s a snapshot of the installed dependencies captured from the package.json file.

VI. Open the package.json file and update the script as follows:

 "scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon src/index.ts"
},

VII. Add Eslint to the project by running the following command:

npm i -g eslint 
npx eslint --init

Follow the prompt to set up linting for your project:

You can also run this command directly using 'npm init @eslint/config@latest'.
Need to install the following packages:
@eslint/create-config@1.0.3
Ok to proceed? (y) y
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · typescript
✔ Where does your code run? · node
The config that you've selected requires the following dependencies:

eslint, globals, @eslint/js, typescript-eslint
✔ Would you like to install them now? · No / Yes
? Which package manager do you want to use? …
npm
❯ yarn
pnpm
bun

Now you should be able to add your rules to your project in the .eslintrc.json or eslint.config.mjs file

{
"env": {
"es2021": true,
"node": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {
"no-console": "warn",
"quotes": ["error", "double"],
"semi": ["error", "always"]
}
}

Note:
If you encounter an error with your tsconfig.json file because you initialized esnlit and got file eslint.config.mjs, just remove that file and create .eslintrc.json and add the content above.

4. Project Cleanup

Before diving into coding, let’s tidy up our frontend project to ensure a clean folder structure.

I. Removing files:
remove the following files with the arrow in the frontend.

II. Cleaning up index.hmtl file and app.ts

Replace the existing index.html file in the public folder with the following code.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>

<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

<title>CodeGenitor</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

replace the App.ts with the following code:

import React from "react";

const App = () => {
return <div>CodeGentitor</div>;
};

export default App;

II. Lets start the application to verify our changes.

It looks like we are having errors but this is good because it shows us that our eslint configuration is working.

Note:
getting comfortable with uncomfortable errors is key to becoming a skilled developer. Challenge yourself to guess the nature of an error before seeking solutions — it’s a great way to grow!

5. Craft UI components for the login/register form. 🚀

Congrats on tackling the first challenge! Stuck? No worries; drop a comment for a helping hand and get the passcode to unlock the next level!

I. Organize your frontend src directory: Create separate folders for ‘pages’ and ‘components’ to enhance project structure.

cd src; mkdir pages components

Note:
You can create the folders manually or by using the CLI. Make sure you’re in the frontend folder in your terminal as demonstrated above.

II. Install Bootstrap: Enhance development speed by installing a UI framework like React Bootstrap for accelerated application building.

yarn add react-bootstrap bootstrap

You can visit react bootstrap documentation for information.

Install Formik and Yup for the form validation with the command below.

yarn add formik yup

III. Import Bootstrap Styles: Bootstrap styles provide a cohesive and visually appealing design for your application’s UI, enhancing its professionalism and usability.

In your App.tsx import bootstrap and give the div a classname as follows:

import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";

const App = () => {
return <div className="container">CodeGentitor</div>;
};

export default App;

IV. Create a register page:
Design and implement the registration page to allow users to create new accounts.

Inside the ‘pages’ folder, create a new directory named ‘register’. Within it, add a file named ‘Register.tsx’ and include the following code

import React from "react";
import { Form, Button } from "react-bootstrap";
import { Formik, Field, FormikProps, FormikHelpers } from "formik";
import * as Yup from "yup";

interface FormValues {
name: string;
email: string;
password: string;
}

const Register: React.FC = () => {
const validationSchema = Yup.object().shape({
name: Yup.string().required("Name is required"),
email: Yup.string()
.email("Invalid email address")
.required("Email is required"),
password: Yup.string().required("Password is required"),
});

const handleSubmit = (
values: FormValues,
{ setSubmitting }: FormikHelpers<FormValues>
) => {
// Handle form submission
console.log(values);
setSubmitting(false);
};

return (
<div className="mx-auto max-w-md space-y-6">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">Create an account</h1>
<p className="text-gray-500 dark:text-gray-400">
Enter your details to get started.
</p>
</div>
<Formik
initialValues={{ name: "", email: "", password: "" }}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ handleSubmit }: FormikProps<FormValues>) => (
<Form className="space-y-4" onSubmit={handleSubmit}>
<Form.Group className="mb-3" controlId="name">
<Form.Label>Name</Form.Label>
<Field type="text" name="name" as={Form.Control} />
<Form.Control.Feedback type="invalid" />
</Form.Group>
<Form.Group className="mb-3" controlId="email">
<Form.Label>Email</Form.Label>
<Field type="email" name="email" as={Form.Control} />
<Form.Control.Feedback type="invalid" />
</Form.Group>
<Form.Group className="mb-3" controlId="password">
<Form.Label>Password</Form.Label>
<Field type="password" name="password" as={Form.Control} />
<Form.Control.Feedback type="invalid" />
</Form.Group>
<Button variant="primary" type="submit" className="w-full">
Sign Up
</Button>
</Form>
)}
</Formik>
</div>
);
};

export default Register;

Now, if you’re wondering why it’s not displaying on your application, open the ‘App.tsx’ file and render the ‘Register’ page to display the registration form. We’ll address this integration properly later on. See below:

import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import Register from "./pages/register/Register";

const App = () => {
return (
<div className="container">
<Register />
</div>
);
};

export default App;

V. Create the Login Page:
Inside the ‘pages’ folder, create a new directory named ‘login’. Within it, add a file named ‘login.tsx’ and include the following code

import React, { FC } from "react";
import { Form, Button } from "react-bootstrap";
import { Formik, Field, FormikProps, FormikHelpers } from "formik";
import * as Yup from "yup";

interface FormValues {
email: string;
password: string;
}

const Login: FC = () => {
const validationSchema = Yup.object().shape({
email: Yup.string().required("Email is required"),
password: Yup.string().required("Password is required"),
});

const handleSubmit = (
values: FormValues,
{ setSubmitting }: FormikHelpers<FormValues>
) => {
// Handle form submission
console.log(values);
setSubmitting(false);
};

return (
<div className="mx-auto max-w-md space-y-6">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">Login in</h1>
<p className="text-gray-500 dark:text-gray-400">
Enter your details to get started.
</p>
</div>
<Formik
initialValues={{ email: "", password: "" }}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ handleSubmit }: FormikProps<FormValues>) => (
<Form className="space-y-4" onSubmit={handleSubmit}>
<Form.Group className="mb-3" controlId="email">
<Form.Label>Email</Form.Label>
<Field type="email" name="email" as={Form.Control} />
</Form.Group>
<Form.Group className="mb-3" controlId="password">
<Form.Label>Password</Form.Label>
<Field type="password" name="password" as={Form.Control} />
</Form.Group>
<Button variant="primary" type="submit" className="w-full">
Sign In
</Button>
</Form>
)}
</Formik>
</div>
);
};

export default Login;

6. Building the Backend:

Let’s build the backend server for the frontend application.

I. Setting Up the Express Server: If you have done the previous setup, then now we can begin by setting up the express server.

  • Organizing Your Backend Code: Creating Folders for Controllers, Models, Routes, Middlewares, and Validations in the ‘src’ Directory. You are do it manually or use the cli when you are in your folder for backend from the terminal.
mkdir src; cd src; mkdir middlewares routes models validations
  • Within the src directory, craft an index.ts file to define the Express server. Below is a boilerplate code snippet for setting up the server.
import express, { Request, Response } from "express";
import dotenv from "dotenv";
import cors from "cors";
import morgan from "morgan";

// Initialize dotenv to access environment variables
dotenv.config();

const app = express();

// middlewares
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors()); // Enable CORS for all requests
app.use(morgan("dev")); // Log all requests to the console

// Get the PORT from the environment variables
// Add PORT=3000 to the .env file
const PORT = process.env.PORT;

// Basic route
app.get("/", (req: Request, res: Response) => {
try {
return res.status(200).json({
message: " Welcome to CodeGenitor API",
});
} catch (error) {
return res.status(500).json({
message: "Internal Server Error",
});
}
});

// Unknown route handler
app.use((req: Request, res: Response) => {
return res.status(404).json({
message: "Route not found",
});
});

// unknonw route handler
app.use((req: Request, res: Response) => {
return res.status(404).json({
message: "Route not found",
});
});

// Start the server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});

Note:
create a .env file inside the backend folder to hold all your environment variables. It is best practice to securely store environment variables, safeguarding sensitive keys and enhancing maintainability. Remember to to do a quick research on any library you do not know at npm

II. Develop Routes and Controllers for User Authentication.
Congratulations on reaching this point! You’ve surpassed a common barrier where many individuals tend to halt their progress.

  • open the controller folder and create a file called user.controller.ts and write the following code below:
import { Request, Response } from "express";

// register a new user
export const register = async (req: Request, res: Response) => {
try {
return res.status(200).json({
message: "User registered successfully",
});
} catch (error) {
return res.status(500).json({
message: "Internal Server Error",
});
}
};

// login a user
export const login = async (req: Request, res: Response) => {
try {
return res.status(200).json({
message: "User logged in successfully",
});
} catch (error) {
return res.status(500).json({
message: "Internal Server Error",
});
}
};
  • open the routes folder and create a file called user.route.ts and write the following code below:
import { Router } from "express";

import { register, login } from "../controller/user.controller";

const userRouter = Router();

// Register a new user
userRouter.post("/register", register);

// Login a user
userRouter.post("/login", login);
  • Now update your index.ts file with the following so that you can visit the various ends created from the route.
import express, { Request, Response } from "express";
import dotenv from "dotenv";
import cors from "cors";
import morgan from "morgan";
import userRouter from "./routes/user.route";

// Initialize dotenv to access environment variables
dotenv.config();

const app = express();

// middlewares
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors()); // Enable CORS for all requests
app.use(morgan("dev")); // Log all requests to the console

// Get the PORT from the environment variables
// Add PORT=3000 to the .env file
const PORT = process.env.PORT;

// Basic route
app.get("/", (req: Request, res: Response) => {
try {
return res.status(200).json({
message: " Welcome to CodeGenitor API",
});
} catch (error) {
return res.status(500).json({
message: "Internal Server Error",
});
}
});

// User routes
app.use("/api/user", userRouter);

// Unknown route handler
app.use((req: Request, res: Response) => {
return res.status(404).json({
message: "Route not found",
});
});

// unknonw route handler
app.use((req: Request, res: Response) => {
return res.status(404).json({
message: "Route not found",
});
});

// Start the server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});

Note:
You can test the rest of the HTTP methods using postman besides the Get where you can do it from the browser. If you do not know about postman, I have added a link to find more about here. comment to let me know if I should go into details with postman.

Sample test of the API

III. Connecting database to the application:

  • To install PostgreSQL, follow these steps:
  1. Visit the PostgreSQL website and navigate to the download section.
  2. Choose the appropriate installation package for your operating system (OS).
  3. Download the PostgreSQL installer and run it.
  4. Follow the installation wizard’s instructions to complete the installation process.
  5. Once installed, you can start PostgreSQL and access it using the command-line interface or graphical tools.
psql server sample
  • Create a db server and start the server:
  • Set up a PostgreSQL database:
  1. Open psql: Open your command-line interface (CLI) or terminal and type psql to enter the PostgreSQL command-line interface.
  2. Create Database: Execute the following SQL command to create a new database:
CREATE USER users with password 'hardpassalwaysok';

CREATE DATABASE users_auth;

GRANT ALL PRIVILEGES ON DATABASE users_auth TO users;

Note:
you can call the psql from your terminal, it doesn’t matter where you open it. And you can list all the databases to verify the db is created by running `\l`

  • Configure the backend to connect to the database:

1. Install pg Package: In the Node.js project directory, install the pg package, which is the PostgreSQL client for Node.js:

npm install pg @types/pg

Note: You can just add pg to the requirement file and run the update by running it again.

  • Configure the backend to connect to the database:
  1. Create a new directory named ‘src’ within the backend folder. Inside this ‘src’ directory, add a file named ‘connectDb.ts’. You can accomplish this either manually or by following the terminal commands provided below:
mkdir config; cd config; touch connectDB.ts

Note:
Assuming you are already in the src folder from the terminal in your backend.

2. Creating the Database connection in connectDB.ts

import { Pool } from "pg";

// configure the database connection
const connectDB = async () => {
const pool = new Pool({
user: "users",
host: "localhost",
database: "users_auth",
password: "hardpassalwaysok",
});

// test the connection
pool.query("SELECT NOW()", (err, res) => {
if (err) {
console.log(err);
} else {
console.log("Database connected successfully");
}
});
};

export default connectDB;

3. Update the index.ts file to see if the database connects successfully.

import express, { Request, Response } from "express";
import dotenv from "dotenv";
import cors from "cors";
import morgan from "morgan";
import userRouter from "./routes/user.route";
import connectDB from "./config/connectDB";

// Initialize dotenv to access environment variables
dotenv.config();

const app = express();

// middlewares
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors()); // Enable CORS for all requests
app.use(morgan("dev")); // Log all requests to the console

// Get the PORT from the environment variables
// Add PORT=3000 to the .env file
const PORT = process.env.PORT;

// Basic route
app.get("/", (req: Request, res: Response) => {
try {
return res.status(200).json({
message: " Welcome to CodeGenitor API",
});
} catch (error) {
return res.status(500).json({
message: "Internal Server Error",
});
}
});

// User routes
app.use("/api/user", userRouter);

// Unknown route handler
app.use((req: Request, res: Response) => {
return res.status(404).json({
message: "Route not found",
});
});

// unknonw route handler
app.use((req: Request, res: Response) => {
return res.status(404).json({
message: "Route not found",
});
});

// Start the server
app.listen(PORT, async () => {
console.log(`Server is running on http://localhost:${PORT}`);
await connectDB(); // connect to the database
});

Note:
If you’ve followed the steps correctly, your backend should now be seamlessly connected to your database. Encountering issues? Don’t hesitate to reach out by dropping a comment or sending a message, and I’ll swiftly assist you to get back on track.

4. Adding a schema to create database tables
· Navigate to the src directory in your backend project.
· Create a new folder named schema.
· Inside the schema folder, create a file named user.schema.sql.
· Open the user.schema.sql file and add the following schema definition:

CREATE SCHEMA users;

CREATE TABLE users.users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL
);

· Update your `connectDB.ts` file as follows:

import { Pool } from "pg";
import chalk from "chalk";
import fs from "fs";
import path from "path";

const pool = new Pool({
user: "users",
host: "localhost",
database: "users_auth",
password: "hardpassalwaysok",
});

// Read the contents of the user.schema.sql file
const schemaFilePath = path.resolve(__dirname, "../schema/user.schema.sql");
const schemaSQL = fs.readFileSync(schemaFilePath, "utf-8");

// Function to create tables defined in the schema file
const createTables = async () => {
try {
// Check if the users table already exists
const result = await pool.query(`
SELECT EXISTS (
SELECT 1
FROM information_schema.tables
WHERE table_schema = 'users' AND table_name = 'users'
)
`);

const tableExists = result.rows[0].exists;

if (!tableExists) {
// Execute the SQL commands to create tables
await pool.query(schemaSQL);
console.log(chalk.greenBright("Tables created successfully"));
} else {
console.log(chalk.yellow("Users table already exists"));
}
} catch (error) {
console.error(chalk.red("Error creating tables:"), error);
}
};

// configure the database connection
const connectDB = async () => {
try {
// Connect to the database
await pool.connect();
console.log(chalk.greenBright("Database connected successfully"));

// Create tables
await createTables();
} catch (error) {
console.error(chalk.red("Error connecting to database:"), error);
}
};

export default connectDB;

IV. Implement user registration and login logic

  • write the following logic for your `user.controller.tsx`
import { Request, Response } from "express";
import { pool } from "../config/connectDB";
import bcrypt from "bcryptjs";

// register a new user
export const register = async (req: Request, res: Response) => {
try {
// get the user data from the request body
const { username, email, password } = req.body;

// check if user data is provided
if (!username || !email || !password) {
return res.status(400).json({
message: "Please provide all user details",
});
}

// check if user already exists
const user = await pool.query("SELECT * FROM users WHERE email = $1", [
email,
]);
if (user.rows.length > 0) {
return res.status(400).json({
message: "User with this email already exists",
});
}

// hash the password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);

// execute SQL query to insert user into the database
const query = `
INSERT INTO users (username, email, password)
VALUES ($1, $2, $3)
`;
await pool.query(query, [username, email, hashedPassword]);

return res.status(200).json({
message: "User registered successfully",
});
} catch (error) {
console.log(error);
return res.status(500).json({
message: "Internal Server Error",
});
}
};

// login a user
export const login = async (req: Request, res: Response) => {
try {
// get the user data from the request body
const { email, password } = req.body;

// check if user data is provided
if (!email || !password) {
return res.status(400).json({
message: "Please provide all user details",
});
}

// check if user exists
const user = await pool.query("SELECT * FROM users WHERE email = $1", [
email,
]);

if (user.rows.length === 0) {
return res.status(400).json({
message: "User does not exist",
});
}

// compare the password
const validPassword = await bcrypt.compare(password, user.rows[0].password);
if (!validPassword) {
return res.status(400).json({
message: "Invalid password",
});
}

// If the email and password are valid, return user data
return res.status(200).json({
message: "User logged in successfully",
user: {
id: user.rows[0].id,
username: user.rows[0].username,
email: user.rows[0].email,
},
});
} catch (error) {
return res.status(500).json({
message: "Internal Server Error",
});
}
};

7. Implement the frontend functionality:

· Create a folder called service and inside it also index.service.ts a file inside the src directory of your frontend project, you can use the following command in your terminal:

mkdir service;cd service; touch index.service.ts
yarn add axios @types/axios

Note: axios package has also been installed from the CLI.

· Updated the index.service.ts file with the following:

import axios from "axios";

const apiUrl = "http://localhost:4000/api/";

interface User {
username: string;
email: string;
password: string;
}

// register the user service
export const registerUser = async (user: User) => {
try {
const response = await axios.post(`${apiUrl}user/register`, user);
return response.data;
} catch (error) {
return error;
}
};

// login the user service
export const loginUser = async (user: User) => {
try {
const response = await axios.post(`${apiUrl}user/login`, user);
return response.data;
} catch (error) {
return error;
}
};

· Next, we’ll enable user registration from our frontend application. Navigate to the Register.tsx file and implement the following updates:

import React, { useState } from "react";
import { Form, Button } from "react-bootstrap";
import { Formik, Field, FormikProps, FormikHelpers } from "formik";
import * as Yup from "yup";
import { registerUser } from "../../service/index.service";

interface FormValues {
username: string;
email: string;
password: string;
}

const Register: React.FC = () => {
const [success, setSuccess] = useState("");

const validationSchema = Yup.object().shape({
username: Yup.string().required("Name is required"),
email: Yup.string()
.email("Invalid email address")
.required("Email is required"),
password: Yup.string().required("Password is required"),
});

const handleSubmit = async (
values: FormValues,
{ setSubmitting }: FormikHelpers<FormValues>
) => {
// Handle form submission
const submit = await registerUser(values);
console.log(submit.message);
setSuccess(submit.message);
setSubmitting(false);
};

return (
<div className="mx-auto max-w-md space-y-6">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">Create an account</h1>
<p className="text-gray-500 dark:text-gray-400">
Enter your details to get started.
</p>
</div>
<Formik
initialValues={{ username: "", email: "", password: "" }}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{({ handleSubmit }: FormikProps<FormValues>) => (
<Form className="space-y-4" onSubmit={handleSubmit}>
{success && (
<div
className="text-green-500 "
style={{
textAlign: "center",
fontSize: "1.2rem",
fontWeight: "bold",
backgroundColor: "#f0f0f0",
padding: "0.5rem",
borderRadius: "0.5rem",
}}
>
{success}
</div>
)}
<Form.Group className="mb-3" controlId="name">
<Form.Label>Username</Form.Label>
<Field type="text" name="username" as={Form.Control} />
<Form.Control.Feedback type="invalid" />
</Form.Group>
<Form.Group className="mb-3" controlId="email">
<Form.Label>Email</Form.Label>
<Field type="email" name="email" as={Form.Control} />
<Form.Control.Feedback type="invalid" />
</Form.Group>
<Form.Group className="mb-3" controlId="password">
<Form.Label>Password</Form.Label>
<Field type="password" name="password" as={Form.Control} />
<Form.Control.Feedback type="invalid" />
</Form.Group>
<Button variant="primary" type="submit" className="w-full">
Sign Up
</Button>
</Form>
)}
</Formik>
</div>
);
};

export default Register;

8. Conclusion

If you have been able to follow all the steps , step by step you should have a results like this below:

Fork the source Code on Github

Lets reflect key points covered:

  1. Frontend Development: We built the frontend using React, setting up pages for user registration and login, along with form validation using Formik and Yup.
  2. Backend Development: The backend was developed using Node.js and Express, with routes for user registration and login, and database integration with PostgreSQL using pg.
  3. Database Setup: PostgreSQL was used as the database, with tables created for storing user information.
  4. User Authentication: User authentication was implemented securely, with passwords hashed before storage using bcrypt.
  5. Connection and Deployment: The frontend and backend were connected, and the application was deployed for testing.

Next steps and additional resources:

  • React Navigation: For more complex applications, consider implementing React Navigation for seamless navigation between screens.
  • Token-based Authentication: Explore token-based authentication for enhanced security and scalability. Libraries like JSON Web Token (JWT) can be used for this purpose.
  • Error Handling: Improve error handling in both frontend and backend to provide better user experience and debug capabilities.
  • Security: Continue to enhance application security, such as implementing HTTPS and input validation to prevent common vulnerabilities.
  • Deployment: Learn about different deployment options, including cloud platforms like Heroku or AWS, to make the application accessible online.

Hopefully, I have been able to help you achieve your aim, share with a friend who could benefit and give me a like and subscribe to help me help more. As the saying goes “what good is knowledge if not shared?”

--

--

CodeGenitor

Software developer passionate about coding, innovation, and tech trends. Turning ideas into reality, one line of code at a time.