Angular 20 CRUD Application Using Fake JSON Server
In this blog, we will build a complete CRUD (Create, Read, Update, Delete) application using Angular 20 and a Fake JSON Server. This is perfect for learning API integration without creating a real backend.
We will cover Angular project setup, fake backend creation, model design, service creation, standalone components, routing, CRUD UI, and common issues faced during development.
⭐ What is JSON Server?
JSON Server is a lightweight tool that allows you to create a fake REST API using a simple JSON file. It supports REST operations like GET, POST, PUT, PATCH, and DELETE, which makes it useful for frontend practice and API testing.
Angular 20 supports modern standalone application structure, and HTTP requests can be handled using Angular HttpClient with provideHttpClient(). :contentReference[oaicite:0]{index=0}
angular-json-crud/
│
├── db.json
├── package.json
└── src/
└── app/
│
├── app.config.ts
├── app.routes.ts
│
├── models/
│ └── user.model.ts
│
├── services/
│ └── user.service.ts
│
└── pages/
├── user-list/
│ ├── user-list.component.ts
│ ├── user-list.component.html
│ └── user-list.component.css
│
└── user-form/
├── user-form.component.ts
├── user-form.component.html
└── user-form.component.css
📌 Step 1: Create Fake Backend (db.json)
Create a file named db.json in your project root directory and add the following content:
{
"users": [
{
"id": "1",
"name": "Umar Rahman",
"email": "umar@gmail.com",
"phone": "9999999999"
}
]
}
Now start the JSON server using the command below:
npx json-server --watch db.json --port 3000
Test the API in your browser:
http://localhost:3000/users
📌 Step 2: Create Angular 20 Project
Create a new Angular project using Angular CLI:
ng new angular-json-crud
Move inside the project folder:
cd angular-json-crud
Run the Angular project:
ng serve
Open the project in browser:
http://localhost:4200
📌 Step 3: Create User Model
Create a file:
src/app/models/user.model.ts
export interface User {
id?: string;
name: string;
email: string;
phone: string;
}
Here, ID is optional because JSON Server automatically creates an ID when we add a new user.
📌 Step 4: Configure HttpClient
Open app.config.ts and add provideHttpClient(). In modern Angular standalone apps, this is the recommended way to make HttpClient available for dependency injection. :contentReference[oaicite:1]{index=1}
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient()
]
};
📌 Step 5: Create User Service
Create a service file:
src/app/services/user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../models/user.model';
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'http://localhost:3000/users';
constructor(private http: HttpClient) {}
// READ
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
// CREATE
addUser(user: User): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}
// UPDATE
updateUser(id: string, user: User): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${id}`, user);
}
// DELETE
deleteUser(id: string): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
// SINGLE USER
getUserById(id: string): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
}
📌 Step 6: Create Routes
Open app.routes.ts and add routes for list, add, and edit pages. Angular Router is the official Angular library for navigation in single-page applications. :contentReference[oaicite:2]{index=2}
import { Routes } from '@angular/router';
import { UserListComponent } from './pages/user-list/user-list.component';
import { UserFormComponent } from './pages/user-form/user-form.component';
export const routes: Routes = [
{
path: '',
component: UserListComponent
},
{
path: 'add-user',
component: UserFormComponent
},
{
path: 'edit-user/:id',
component: UserFormComponent
}
];
📌 Step 7: User List Component
Create component:
ng generate component pages/user-list
user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';
import { UserService } from '../../services/user.service';
import { User } from '../../models/user.model';
@Component({
selector: 'app-user-list',
standalone: true,
imports: [CommonModule, RouterLink],
templateUrl: './user-list.component.html',
styleUrl: './user-list.component.css'
})
export class UserListComponent implements OnInit {
users: User[] = [];
isLoading = false;
errorMessage = '';
constructor(private userService: UserService) {}
ngOnInit(): void {
this.getUsers();
}
getUsers(): void {
this.isLoading = true;
this.userService.getUsers().subscribe({
next: (data) => {
this.users = data;
this.isLoading = false;
},
error: (error) => {
this.errorMessage = 'Something went wrong while fetching users';
this.isLoading = false;
console.error(error);
}
});
}
deleteUser(id: string | undefined): void {
if (!id) return;
if (confirm('Are you sure you want to delete this user?')) {
this.userService.deleteUser(id).subscribe({
next: () => {
this.getUsers();
},
error: (error) => {
console.error(error);
alert('Failed to delete user');
}
});
}
}
}
user-list.component.html
<div class="container">
<div class="header">
<h2>Angular 20 CRUD Users</h2>
<a routerLink="/add-user" class="add-btn">Add User</a>
</div>
<div *ngIf="isLoading" class="loading">
Loading users...
</div>
<div *ngIf="errorMessage" class="error">
{{ errorMessage }}
</div>
<table *ngIf="!isLoading && users.length > 0">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users">
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>{{ user.phone }}</td>
<td>
<a [routerLink]="['/edit-user', user.id]" class="edit-btn">Edit</a>
<button (click)="deleteUser(user.id)" class="delete-btn">Delete</button>
</td>
</tr>
</tbody>
</table>
<div *ngIf="!isLoading && users.length === 0" class="no-data">
No users found
</div>
</div>
user-list.component.css
.container {
width: 90%;
max-width: 900px;
margin: 40px auto;
font-family: Arial, sans-serif;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.add-btn,
.edit-btn,
.delete-btn {
padding: 8px 14px;
border: none;
text-decoration: none;
border-radius: 5px;
cursor: pointer;
color: white;
font-size: 14px;
}
.add-btn {
background: #2563eb;
}
.edit-btn {
background: #16a34a;
margin-right: 8px;
}
.delete-btn {
background: #dc2626;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 25px;
}
th,
td {
padding: 12px;
border: 1px solid #ddd;
text-align: left;
}
th {
background: #f3f4f6;
}
.loading,
.error,
.no-data {
margin-top: 20px;
font-size: 18px;
}
.error {
color: red;
}
📌 Step 8: Add / Edit User Component
Create component:
ng generate component pages/user-form
user-form.component.ts
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { UserService } from '../../services/user.service';
import { User } from '../../models/user.model';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [CommonModule, FormsModule, RouterLink],
templateUrl: './user-form.component.html',
styleUrl: './user-form.component.css'
})
export class UserFormComponent implements OnInit {
user: User = {
name: '',
email: '',
phone: ''
};
userId: string | null = null;
isEditMode = false;
isLoading = false;
constructor(
private userService: UserService,
private router: Router,
private route: ActivatedRoute
) {}
ngOnInit(): void {
this.userId = this.route.snapshot.paramMap.get('id');
if (this.userId) {
this.isEditMode = true;
this.getUser(this.userId);
}
}
getUser(id: string): void {
this.isLoading = true;
this.userService.getUserById(id).subscribe({
next: (data) => {
this.user = data;
this.isLoading = false;
},
error: (error) => {
console.error(error);
alert('User not found');
this.isLoading = false;
}
});
}
saveUser(): void {
if (!this.user.name || !this.user.email || !this.user.phone) {
alert('All fields are required');
return;
}
this.isLoading = true;
if (this.isEditMode && this.userId) {
this.userService.updateUser(this.userId, this.user).subscribe({
next: () => {
this.router.navigate(['/']);
},
error: (error) => {
console.error(error);
alert('Failed to update user');
this.isLoading = false;
}
});
} else {
this.userService.addUser(this.user).subscribe({
next: () => {
this.router.navigate(['/']);
},
error: (error) => {
console.error(error);
alert('Failed to add user');
this.isLoading = false;
}
});
}
}
}
user-form.component.html
<div class="form-container">
<h2>{{ isEditMode ? 'Update User' : 'Add User' }}</h2>
<form (ngSubmit)="saveUser()">
<div class="form-group">
<label>Name</label>
<input
type="text"
name="name"
[(ngModel)]="user.name"
placeholder="Enter name"
/>
</div>
<div class="form-group">
<label>Email</label>
<input
type="email"
name="email"
[(ngModel)]="user.email"
placeholder="Enter email"
/>
</div>
<div class="form-group">
<label>Phone</label>
<input
type="text"
name="phone"
[(ngModel)]="user.phone"
placeholder="Enter phone"
/>
</div>
<button type="submit" [disabled]="isLoading">
{{ isLoading ? 'Saving...' : 'Save' }}
</button>
<a routerLink="/" class="back-btn">Back</a>
</form>
</div>
user-form.component.css
.form-container {
width: 90%;
max-width: 500px;
margin: 40px auto;
font-family: Arial, sans-serif;
padding: 25px;
border: 1px solid #ddd;
border-radius: 8px;
}
h2 {
margin-bottom: 20px;
}
.form-group {
margin-bottom: 16px;
}
label {
display: block;
margin-bottom: 6px;
font-weight: bold;
}
input {
width: 100%;
padding: 10px;
border: 1px solid #bbb;
border-radius: 5px;
}
button {
padding: 10px 18px;
background: #2563eb;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:disabled {
background: #94a3b8;
}
.back-btn {
margin-left: 10px;
text-decoration: none;
color: #2563eb;
}
📌 Step 9: Add Router Outlet
Open app.component.html and replace everything with:
<router-outlet></router-outlet>
Make sure RouterOutlet is imported in app.component.ts:
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent {
title = 'angular-json-crud';
}
📌 Step 10: Output
After running both Angular and JSON Server, you will be able to:
- Display users from fake API
- Add new user
- Edit existing user
- Delete user
- Refresh data automatically after CRUD operations
⭐ Common Issues and Fixes
- HttpClient not working: Add
provideHttpClient()inapp.config.ts. - ngModel not working: Import
FormsModulein the standalone component. - *ngFor or *ngIf not working: Import
CommonModulein the standalone component. - API not loading: Make sure JSON Server is running on
http://localhost:3000. - CORS issue: Use JSON Server locally or enable proper CORS support.
- ID error: Always treat ID as
stringbecause JSON Server may use string or generated IDs.
📌 Final Output
- Fake REST API using JSON Server
- Angular 20 CRUD operations
- Standalone component-based structure
- Service-based API integration
- Clean routing for add and edit pages
- Beginner-friendly real-world API practice
📌 Summary
In this blog, we learned how to build a complete Angular 20 CRUD application using Fake JSON Server. This project is very useful for beginners who want to understand real-world API integration, service creation, routing, and standalone component-based Angular development.
This setup is also helpful before moving to a real backend like Node.js, Laravel, Django, or Spring Boot.
