Let's expand on the functionality of the full-stack app by implementing authentication using JWT, detailed CRUD operations, and securing the API routes. This expansion will help create a more robust application, especially for handling sensitive employee data.
Part 1: Implementing Authentication Using JWT
1. Backend - Add Employee Authentication Routes
We'll add API routes for login, registration, and securing endpoints with JWT token verification.
1.1. Employee Registration API
Add a new API route for registering employees. Create pages/api/employees/register.ts.
// backend/pages/api/employees/register.ts
import { NextApiRequest, NextApiResponse } from 'next';
import bcrypt from 'bcryptjs';
import { AppDataSource } from '../../../data-source';
import { Employee } from '../../../entities/Employee';
AppDataSource.initialize();
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'POST') {
const { name, email, password, position } = req.body;
const employeeRepository = AppDataSource.getRepository(Employee);
// Check if the email is already registered
const existingEmployee = await employeeRepository.findOne({ where: { email } });
if (existingEmployee) {
return res.status(400).json({ message: 'Email is already in use.' });
}
// Hash password before saving
const hashedPassword = bcrypt.hashSync(password, 8);
const newEmployee = employeeRepository.create({
name,
email,
password: hashedPassword,
position,
isActive: true,
});
await employeeRepository.save(newEmployee);
res.status(201).json(newEmployee);
} else {
res.status(405).json({ message: 'Method Not Allowed' });
}
}
1.2. Employee Login API
Create a login endpoint where employees can authenticate and receive a JWT token. Create pages/api/employees/login.ts.
// backend/pages/api/employees/login.ts
import { NextApiRequest, NextApiResponse } from 'next';
import bcrypt from 'bcryptjs';
import { AppDataSource } from '../../../data-source';
import { Employee } from '../../../entities/Employee';
import { generateToken } from '../../../utils/jwt';
AppDataSource.initialize();
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'POST') {
const { email, password } = req.body;
const employeeRepository = AppDataSource.getRepository(Employee);
// Check if the employee exists
const employee = await employeeRepository.findOne({ where: { email } });
if (!employee) {
return res.status(400).json({ message: 'Invalid email or password' });
}
// Check password
const isPasswordValid = bcrypt.compareSync(password, employee.password);
if (!isPasswordValid) {
return res.status(400).json({ message: 'Invalid email or password' });
}
// Generate JWT token
const token = generateToken(employee);
res.status(200).json({ token });
} else {
res.status(405).json({ message: 'Method Not Allowed' });
}
}
2. Securing Routes with JWT
To secure our routes, we'll create a middleware that verifies the JWT token before allowing access to protected routes.
2.1. JWT Verification Middleware
Create a middleware that checks the token. You can add this in utils/verifyToken.ts.
// backend/utils/verifyToken.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { verifyToken } from './jwt';
export const verifyJwt = (handler) => async (req: NextApiRequest, res: NextApiResponse) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
try {
const decoded = verifyToken(token);
req.user = decoded; // Attach the decoded token data to the request object
return handler(req, res);
} catch (error) {
return res.status(401).json({ message: 'Invalid token' });
}
};
2.2. Securing Employee CRUD Routes
To secure CRUD routes, we’ll modify pages/api/employees/index.ts to only allow requests with a valid token.
// backend/pages/api/employees/index.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { AppDataSource } from '../../../data-source';
import { Employee } from '../../../entities/Employee';
import { verifyJwt } from '../../../utils/verifyToken';
AppDataSource.initialize();
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const employeeRepository = AppDataSource.getRepository(Employee);
if (req.method === 'GET') {
const page = parseInt(req.query.page as string) || 1;
const take = 10;
const skip = (page - 1) * take;
const [result, total] = await employeeRepository.findAndCount({
take: take,
skip: skip,
});
res.status(200).json({
data: result,
total,
page,
pages: Math.ceil(total / take),
});
}
if (req.method === 'POST') {
const { name, email, password, position } = req.body;
const hashedPassword = bcrypt.hashSync(password, 8);
const newEmployee = employeeRepository.create({
name,
email,
password: hashedPassword,
position,
isActive: true,
});
await employeeRepository.save(newEmployee);
res.status(201).json(newEmployee);
}
};
export default verifyJwt(handler);
With this, all the routes will be protected, and the user must provide a valid JWT token in the request's Authorization header.
Part 2: Frontend - Implementing Login and Authentication in Ionic
1. Add Login Functionality
To allow employees to log in, we’ll create a login page and store the JWT token in local storage.
1.1. Create Login Page
Generate a new page in your Ionic app:
ionic g page login
In login.page.html, create the login form:
<ion-header>
<ion-toolbar>
<ion-title>Login</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-item>
<ion-label position="floating">Email</ion-label>
<ion-input type="email" [(ngModel)]="email"></ion-input>
</ion-item>
<ion-item>
<ion-label position="floating">Password</ion-label>
<ion-input type="password" [(ngModel)]="password"></ion-input>
</ion-item>
<ion-button expand="full" (click)="login()">Login</ion-button>
</ion-content>
1.2. Implement Login Logic
In login.page.ts, use the EmployeeService to log in the user and store the JWT token.
// src/app/pages/login/login.page.ts
import { Component } from '@angular/core';
import { EmployeeService } from '../../services/employee.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-login',
templateUrl: './login.page.html',
styleUrls: ['./login.page.scss'],
})
export class LoginPage {
email = '';
password = '';
constructor(private employeeService: EmployeeService, private router: Router) {}
async login() {
try {
const { token } = await this.employeeService.login(this.email, this.password);
localStorage.setItem('token', token); // Store the JWT token
this.router.navigate(['/employee-list']); // Redirect to employee list after login
} catch (error) {
console.log('Login failed:', error.message);
}
}
}
1.3. Add Login Functionality to EmployeeService
In employee.service.ts, add the login method.
// src/app/services/employee.service.ts
async login(email: string, password: string) {
try {
const response = await axios.post(`${this.apiUrl}/login`, { email, password });
return response.data;
} catch (error) {
console.error('Login error:', error);
throw error;
}
}
2. Protect Routes in Ionic
We can use Angular guards to protect certain routes in the Ionic app.
2.1. Create an Auth Guard
Generate an AuthGuard that checks if the user is authenticated before accessing certain pages.
ionic g guard auth
In auth.guard.ts, implement the logic to check for the JWT token in local storage.
// src/app/guards/auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean {
const token = localStorage.getItem('token');
if (!token) {
this.router.navigate(['/login']);
return false;
}
return true;
}
}
2.2. Protect Employee List Route
In app-routing.module.ts, protect the employee-list route using the AuthGuard.
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule,
Routes } from '@angular/router';
import { AuthGuard } from './guards/auth.guard';
const routes: Routes = [
{
path: 'employee-list',
loadChildren: () => import('./pages/employee-list/employee-list.module').then(m => m.EmployeeListPageModule),
canActivate: [AuthGuard],
},
{
path: 'login',
loadChildren: () => import('./pages/login/login.module').then(m => m.LoginPageModule),
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })],
exports: [RouterModule],
})
export class AppRoutingModule {}
Part 3: Detailed Employee CRUD Operations
In the backend, let's add the PUT (update) and DELETE routes for employees.
1. Update Employee API
Add a PUT route to update employee details. In pages/api/employees/[id].ts, add the following:
// backend/pages/api/employees/[id].ts
import { NextApiRequest, NextApiResponse } from 'next';
import { AppDataSource } from '../../../data-source';
import { Employee } from '../../../entities/Employee';
import { verifyJwt } from '../../../utils/verifyToken';
AppDataSource.initialize();
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const employeeRepository = AppDataSource.getRepository(Employee);
if (req.method === 'PUT') {
const { id } = req.query;
const { name, email, position, isActive } = req.body;
const employee = await employeeRepository.findOne({ where: { id: Number(id) } });
if (!employee) {
return res.status(404).json({ message: 'Employee not found' });
}
employee.name = name || employee.name;
employee.email = email || employee.email;
employee.position = position || employee.position;
employee.isActive = isActive !== undefined ? isActive : employee.isActive;
await employeeRepository.save(employee);
res.status(200).json(employee);
} else {
res.status(405).json({ message: 'Method Not Allowed' });
}
};
export default verifyJwt(handler);
2. Delete Employee API
Similarly, add a DELETE route in the same file.
// backend/pages/api/employees/[id].ts
if (req.method === 'DELETE') {
const { id } = req.query;
const employee = await employeeRepository.findOne({ where: { id: Number(id) } });
if (!employee) {
return res.status(404).json({ message: 'Employee not found' });
}
await employeeRepository.remove(employee);
res.status(204).send();
}
3. Implementing Update and Delete in Ionic
In employee.service.ts, add methods to update and delete employees.
// src/app/services/employee.service.ts
async updateEmployee(id: number, employee) {
const token = localStorage.getItem('token');
const response = await axios.put(`${this.apiUrl}/${id}`, employee, {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
}
async deleteEmployee(id: number) {
const token = localStorage.getItem('token');
await axios.delete(`${this.apiUrl}/${id}`, {
headers: { Authorization: `Bearer ${token}` },
});
}
Final Thoughts
By now, you have expanded the app to include:
- Authentication using JWT (both login and registration).
- Protected API routes to ensure only authenticated users can access them.
- Full CRUD functionality for the employee model, including Create, Read, Update, and Delete operations.
- Authorization in the frontend using Angular Guards, and properly managing tokens with local storage.
This app can be further improved by adding:
- Input validation (both frontend and backend).
- Password reset functionality.
- Role-based access control if there are different roles (e.g., Admin, User) in your system.