How can I help you?
Connecting the Angular Grid with Express.js Server
11 Apr 202624 minutes to read
Express.js is a lightweight and flexible Node.js web application framework that provides a simple set of features for building REST APIs quickly. Express.js follows a traditional REST API approach with multiple endpoints for different data operations.
Application architecture:
- Backend: Express.js server (Node.js) - Handles REST API endpoints, patient data CRUD operations, and business logic.
-
Frontend: Angular application - Displays the Syncfusion® Grid UI with
UrlAdaptorfor seamless data binding. - Data Model: Hospital Patient Management System with comprehensive patient information.
Prerequisites
| Software / Package | Recommended version | Purpose |
|---|---|---|
| Node.js | 20.x LTS or later | Runtime |
| npm / yarn / pnpm | 11.x or later | Package manager |
| Angular CLI | 18.x or later | Frontend framework and CLI tools |
| TypeScript | 5.x or later | Server‑side and client‑side type safety |
Key topics
| # | Topics | Link |
|---|---|---|
| 1 | Create a Node.js project, configure TypeScript, and set up Express server | View |
| 2 | Handle server-side filtering, searching, sorting, and paging for efficient data processing | View |
| 3 | Handle server-side CRUD actions | View |
| 4 | Start servers locally and access the backend | View |
| 5 | Create an Angular project and configure Syncfusion® Grid with UrlAdaptor | View |
| 6 | Start servers locally and access the frontend | View |
| 7 | Explore a complete working sample on GitHub | View |
Setting up the Express.js backend using Node.js
The Express.js backend acts as the central REST API service, handling HTTP requests and responses that power the Syncfusion® Angular Grid.
Step 1: Create the Express server and install required packages
Before configuring the Express.js API, a proper project structure must be created to host the backend server. This folder will contain the server configuration, required dependencies, and sample data used for processing API requests.
For this implementation, an Express.js server is created to manage a Hospital Patient Management System with comprehensive patient data including demographics, medical information, doctor assignments, and hospital details.
Create project root folder:
Open a terminal (for example, an integrated terminal in Visual Studio Code, Windows Command Prompt opened with Win+R or macOS Terminal launched with Cmd+Space) and run the following commands:
mkdir ej2-angular-grid-with-express-js
cd ej2-angular-grid-with-express-jsCreate backend server folder:
Create the server folder with all necessary subdirectories for organizing code by responsibility:
mkdir server
cd server
mkdir src
cd src
mkdir controllers routes utils types
cd ..The folder structure after this step should look like:
ej2-angular-grid-with-express-js/
├── server/
│ └── src/
│ ├── controllers/
│ ├── routes/
│ ├── utils/
│ └── types/
Initialize Node.js and install packages:
Run the following commands in the terminal window (ensure it is in the server directory) to install the required packages.
npm init -y
npm install express cors
npm install -D typescript ts-node nodemon @types/express @types/cors @types/nodePackage descriptions:
-
express– Web framework for building REST APIs and defining HTTP routes. -
cors– Enables cross-origin requests from the Angular client to the Express server. -
typescript, ts-node, nodemon– Enables TypeScript-based development with auto-reload on file changes. -
@types/express, @types/cors, @types/node– Type definitions for Express, CORS, and Node.js.
The server folder is now created with all required subdirectories, and packages are installed. The project is ready for setting up TypeScript configuration, defining data interfaces, creating API routes, and implementing controller logic.
Current folder structure:
ej2-angular-grid-with-express-js/
└── server/
├── node_modules/
├── src/
│ ├── controllers/
│ ├── routes/
│ ├── utils/
│ └── types/
├── package.json
└── package-lock.json
Step 2: Configure TypeScript
TypeScript configuration tells the compiler to convert TypeScript to JavaScript and sets up the project structure.
Create tsconfig.json in the server folder using the below command:
npx tsc --initReplace (server/tsconfig.json) file content with the following configuration:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Step 3: Create TypeScript interfaces
Express.js requires clear data type definitions to ensure type safety throughout the application. Create a new file at (server/src/types/interface.ts) with the following content.
export interface Patient {
PatientID: number;
PatientName: string;
Age: number;
Gender: string;
Email: string;
Phone: string;
DoctorName: string;
Specialty: string;
HospitalName: string;
City: string;
Country: string;
AdmissionDate: string;
Diagnosis: string;
Status: string;
}
export interface DataManagerRequest {
skip?: number;
take?: number;
sorted?: Array<{ name: string; direction: string }>;
where?: any[];
search?: Array<{ fields: string[]; key: string; operator: string; ignoreCase: boolean }>;
requiresCounts?: boolean;
}Step 4: Generate sample data
Create a new file at (server/src/utils/data.ts) with the following content.
import { Patient } from "../types/interface";
const patientNames = [
"John Smith", "Sarah Johnson", "Michael Brown", "Emily Davis", "Robert Wilson", ...];
const doctorNames = [
"Dr. James Johnson", "Dr. Sarah Williams", "Dr. Michael Brown", "Dr. Emily Davis", ...];
const hospitalNames = [
"City General Hospital", "St. Mary's Medical Center", "County Health Hospital", ...];
const specialties = [
"Cardiology", "Neurology", "Orthopedics", "General Surgery", "Pediatrics", ...];
const diagnoses = [
"Hypertension", "Diabetes Type 2", "Coronary Artery Disease", "Pneumonia", "Asthma", ...];
const genders = ["Male", "Female"];
const cities = [
"New York", "Los Angeles", "Chicago", "Houston", "Phoenix",
"Philadelphia", "San Antonio", ...];
const countries = ["USA", "Canada", "UK", "Australia", "Germany", "France", "Spain", "Italy"];
const statuses = ["Active", "Discharged", "Under Observation", "Critical", "Stable", "Recovering"];
export function generatePatients(count: number): Patient[] {
let patients: Patient[] = Array.from({ length: count }, (_, i) => {
const admissionDate = new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1);
return {
PatientID: 1001 + i,
PatientName: patientNames[i % patientNames.length],
Age: Math.floor(Math.random() * 70) + 18,
Gender: genders[Math.floor(Math.random() * genders.length)],
Email: `patient${1001 + i}@hospital.com`,
DoctorName: doctorNames[Math.floor(Math.random() * doctorNames.length)],
...
};
});
return patients;
}Purpose: This utility generates “1000” patient records in-memory, eliminating the need for a real database during development and testing.
Step 5: Setup npm scripts for development
Scripts in package.json make it easy to run and build the server. Update the (server/package.json) file with the following scripts.
{
"scripts": {
"dev": "nodemon --exec ts-node src/server.ts",
"start": "ts-node src/server.ts",
"build": "tsc"
}
}Step 6: Create the Express server configuration
Create the main server file at (server/server.ts) that initializes Express.js, sets up middleware, and defines routes.
import express, { Application } from 'express';
import cors from 'cors';
import patientRoutes from './src/routes/patients.routes';
const app: Application = express();
const PORT = 5000;
app.use(cors({
origin: '*',
methods: ['POST'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/api/patients', patientRoutes);
app.listen(PORT, () => {
console.log(`Patients endpoint: http://localhost:${PORT}/api/patients`);
});
export default app;Step 7: Create API routes
Create a routes file at (server/src/routes/patients.routes.ts) that defines all HTTP endpoints for data operations.
import { Router } from 'express';
import { getPatients, createPatient, updatePatient,deletePatient} from '../controllers/patients.controller';
const router = Router();
// Used to get the data
router.post('/', (req, res) => {
return getPatients(req, res);
});
// Used for Insert operation
router.post('/create', (req, res) => { return createPatient(req, res); });
// Used for Edit operation
router.post('/update', (req, res) => { return updatePatient(req, res); });
// Used for Delete operation
router.post('/remove', (req, res) => { return deletePatient(req, res); });
export default router;Performing Data operations
In the Angular application, interactions with the Grid trigger the UrlAdaptor to generate structured HTTP request payloads, which the Express.js backend processes to perform filtering, searching, sorting, and paging operations on the server side.
Create the controller file at (server/src/controllers/patients.controller.ts) to handle all server request operations for data actions. The complete code is available here.
Filtering
Filtering in the Angular client narrows the dataset by applying conditions (equals, contains, starts with, etc.) with AND/OR logic.
const applyWhere = (data: Patient[], where: any[]): Patient[] => {
if (!where || where.length === 0) return data;
return data.filter(item => {
return evaluatePredicateGroup(item, where, 'and');
});
};Searching
Searching in the Angular client quickly locates records by checking an entered term across multiple columns.
const applySearch = (data: Patient[], search: any[]): Patient[] => {
if (!search || search.length === 0) return data;
return data.filter(item => {
return search.every(s => {
const fields = s.fields || [];
const key = s.key || '';
const operator = s.operator || 'contains';
const ignoreCase = s.ignoreCase !== false;
if (!fields.length || !key) return true;
const searchKey = ignoreCase ? key.toLowerCase() : key;
return fields.some((field: string) => {
const fieldValue = ignoreCase && typeof item[field as keyof Patient] === 'string'
? (item[field as keyof Patient] as string).toLowerCase()
: item[field as keyof Patient];
if (operator === 'contains') {
return String(fieldValue).includes(searchKey);
} else {
return String(fieldValue) === searchKey;
}
});
});
});
};Sorting
Sorting in the Angular Grid arranges records by one or more columns in ascending or descending order.
const applySort = (data: Patient[], sorted: any[]): Patient[] => {
if (!sorted || sorted.length === 0) return data;
const result = [...data];
sorted.forEach(sort => {
const direction = (sort.direction || 'ascending').toLowerCase() === 'descending' ? -1 : 1;
result.sort((a, b) => {
const aVal = a[sort.name as keyof Patient];
const bVal = b[sort.name as keyof Patient];
if (aVal < bVal) return -direction;
if (aVal > bVal) return direction;
return 0;
});
});
return result;
};Paging
Paging divides large datasets into smaller, manageable pages based on offset and page size. This allows the Angular Grid to display data efficiently without loading all records at once.
const skip: number = dm.skip as number ;
const take = dm.take as number;
result = result.slice(skip, skip + take);The “getPatients” method fetches all patient data from the server to support all related actions.
export const getPatients = (req: Request, res: Response) => {
try {
const dm: DataManagerRequest = req.body || {};
let result = [...patients];
// Apply filters
if (dm.where && dm.where.length > 0) {
result = applyWhere(result, dm.where);
}
// Apply search
if (dm.search && dm.search.length > 0) {
result = applySearch(result, dm.search);
}
const count = result.length;
// Apply sort
if (dm.sorted && dm.sorted.length > 0) {
result = applySort(result, dm.sorted);
}
// Apply page
const skip: number = dm.skip as number ;
const take = dm.take as number;
result = result.slice(skip, skip + take);
res.json(dm.requiresCounts ? {result: result, count: count} : patients);
} catch (error) {
res.status(500).json({
error: 'Failed to retrieve patients',
result: [],
count: 0
});
}
};Performing CRUD operations
CRUD operations (Create, Read, Update, Delete) are handled by the controllers and routed through corresponding API endpoints. When the Grid in the Angular application is interacted with, the UrlAdaptor automatically sends the appropriate HTTP request to the backend.
Insert
Insert operation creates a new patient record.
export const createPatient = (req: Request, res: Response) => {
try {
const updatedRecord = req.body.value || req.body;
if (!updatedRecord.PatientName || !updatedRecord.DoctorName) {
return res.status(422).json({
message: 'PatientName and DoctorName are required'
});
}
const newId = Math.max(...patients.map(p => p.PatientID), 1000) + 1;
const newPatient: Patient = {
PatientID: newId,
PatientName: updatedRecord.PatientName,
Age: updatedRecord.Age,
...
};
patients.push(newPatient);
res.status(201).json(newPatient);
} catch (error) {
res.status(422).json({
message: 'Insert failed: ' + (error instanceof Error ? error.message : String(error))
});
}
};Update
Update operation modifies an existing patient record.
export const updatePatient = (req: Request, res: Response) => {
try {
const updatedData = req.body.value || req.body;
const id = req.body.key || req.params.id || updatedData.patientId;
if (!id) return res.status(422).json({ message: 'Missing patientId' });
const index = patients.findIndex((p) => p.patientId === parseInt(id));
patients[index] = { ...patients[index], ...updatedData };
res.json(patients[index]);
} catch (error) {
res.status(422).json({ message: 'Update failed: ' + (error instanceof Error ? error.message : String(error)) });
}
};Delete
Delete operation removes a patient record from the dataset.
export const deletePatient = (req: Request, res: Response) => {
try {
const id = req.body.key;
if (!id) {
return res.status(422).json({
message: 'Missing PatientID'
});
}
const index = patients.findIndex(p => p.PatientID === id);
if (index === -1) {
return res.status(404).json({ error: 'Patient not found' });
}
const deleted = patients[index];
patients.splice(index, 1);
res.status(200).json({
message: 'Patient deleted',
deleted
});
} catch (error) {
res.status(422).json({
message: 'Delete failed: ' + (error instanceof Error ? error.message : String(error))
});
}
};Run the server application
Use the following command to run the application in the browser.
cd ej2-angular-grid-with-express-js/server
npm run devThe server will start on http://localhost:5000.
Patients endpoint: http://localhost:5000/api/patients.
Connecting Syncfusion Angular Grid with Express.js
Create a new Angular application using Angular CLI, which provides a faster development environment and optimized builds.
Step 1: Create Angular application
Open a terminal (for example, an integrated terminal in Visual Studio Code, Windows Command Prompt opened with Win+R or macOS Terminal launched with Cmd+Space) and run the following commands (navigate to the root ej2-angular-grid-with-express-js directory first):
ng new client --routing --style=css
cd clientThis command creates an Angular application named client with routing and CSS support, providing the essential folder structure and files required to begin development immediately.
Step 2: Install Syncfusion packages
Install the necessary Syncfusion® packages using npm:
npm install @syncfusion/ej2-angular-grids --save
npm install @syncfusion/ej2-data --saveStep 3: Include Syncfusion stylesheets
Once dependencies are installed, include the required CSS files in the src/styles.css file:
@import '@syncfusion/ej2-base/styles/material3.css';
@import '@syncfusion/ej2-buttons/styles/material3.css';
@import '@syncfusion/ej2-calendars/styles/material3.css';
@import '@syncfusion/ej2-dropdowns/styles/material3.css';
@import '@syncfusion/ej2-inputs/styles/material3.css';
@import '@syncfusion/ej2-navigations/styles/material3.css';
@import '@syncfusion/ej2-popups/styles/material3.css';
@import '@syncfusion/ej2-splitbuttons/styles/material3.css';
@import '@syncfusion/ej2-notifications/styles/material3.css';
@import '@syncfusion/ej2-angular-grids/styles/material3.css';For this project, the “Material 3” theme is applied. Other themes can be selected, or the existing theme can be customized to meet specific project requirements. For detailed guidance on theming and customization, refer to the Syncfusion angular Components Appearance documentation.
Step 4: Configure DataManager for REST API communication
The Syncfusion® DataManager acts as a communication layer between the Angular Grid and backend services. It sends all Grid operations such as reading data, sorting, filtering, searching, paging, and performing CRUD actions—to the server in a standardized format.
The UrlAdaptor is a built‑in adaptor that formats requests for REST‑style endpoints, such as the Express.js REST Framework API. It defines the request structure and processes the server responses. The UrlAdaptor converts Grid actions into HTTP POST requests and handles the JSON responses returned by the server, enabling the Grid to manage and display data seamlessly.
When using DataManager with UrlAdaptor, the server is expected to return a specific response structure:
- result: The list of data displayed in the current view, supporting on‑demand loading for large datasets.
- count: The total count of records in the dataset.
This response format ensures seamless interaction between the Angular Grid and backend services, enabling all data operations to work consistently.
Create the data manager service file at (client/src/services/dataManager.ts).
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
// API service endpoint
const API_BASE_URL = 'http://localhost:5000/api/patients';
export const patientDataManager = new DataManager({
url: API_BASE_URL, // POST endpoint for featching data with filters, sort, paging
insertUrl: API_BASE_URL + '/create', // POST endpoint for inserting new records
updateUrl: API_BASE_URL + '/update', // POST endpoint for updating records
removeUrl: API_BASE_URL + '/remove', // POST endpoint for deleting records
adaptor: new UrlAdaptor() // Activates the UrlAdaptor
});Step 5: Create the Grid component
Create an Angular component that renders the Syncfusion® Grid and connects it to the Express.js backend at (client/src/app/components/patient-grid.component.ts).
import { Component } from '@angular/core';
import { GridModule, PageService, SortService, FilterService, EditService, ToolbarService, SearchService } from '@syncfusion/ej2-angular-grids';
import { patientDataManager } from '../services/dataManager';
@Component({
selector: 'app-patient-grid',
standalone: true,
imports: [GridModule],
template: `
<ejs-grid [dataSource]="dataManager">
<e-columns>
<e-column field="PatientID" headerText="ID" type="number" isPrimaryKey="true" visible="false"></e-column>
<e-column field="PatientName" headerText="Patient Name" width="150"></e-column>
<e-column field="Age" headerText="Age" type="number" width="80"></e-column>
<e-column field="Gender" headerText="Gender" width="100"></e-column>
<e-column field="DoctorName" headerText="Doctor" width="150"></e-column>
<e-column field="Specialty" headerText="Specialty" width="120"></e-column>
<e-column field="Status" headerText="Status" width="100"></e-column>
</e-columns>
</ejs-grid>
`
})
export class PatientGridComponent {
public dataManager = patientDataManager;
}Step 6: Enable paging
The paging feature divides Grid records into multiple pages, improving performance and usability when handling large datasets. Enable paging by setting the allowPaging property to true and injecting the PageService module into the providers property. Without the PageService module, the pager will not render. Customize pager behavior using the pageSettings property.
import { Component } from '@angular/core';
import { GridModule, PageService } from '@syncfusion/ej2-angular-grids';
import { patientDataManager } from '../services/dataManager';
@Component({
selector: 'app-patient-grid',
standalone: true,
imports: [GridModule],
providers: [PageService],
template: `
<ejs-grid
[dataSource]="dataManager"
[allowPaging]="true"
>
</ejs-grid>
`
})When paging is performed in the Grid, a request is sent to the server with the following payload.

Step 7: Enable filtering
The filtering feature enables searching and refining Grid records based on column values. Enable filtering by setting the allowFiltering property to true and injecting the FilterService module into the providers property. Without the FilterService module, the filter bar will not render. Customize filtering options using the filterSettings property.
import { Component } from '@angular/core';
import { GridModule, FilterService } from '@syncfusion/ej2-angular-grids';
import { patientDataManager } from '../services/dataManager';
@Component({
selector: 'app-patient-grid',
standalone: true,
imports: [GridModule],
providers: [FilterService],
template: `
<ejs-grid
[dataSource]="dataManager"
[allowFiltering]="true"
>
</ejs-grid>
`
})When filtering is performed in the Grid, a request is sent to the server with the following payload.

Step 8: Enable searching
Enabling the search functionality involves integrating a search text box directly into the grid’s toolbar. This allows entering search criteria conveniently within the grid interface. To add the search item to the grid’s toolbar, use the toolbar property and add Search item.
import { Component } from '@angular/core';
import { GridModule, ToolbarService } from '@syncfusion/ej2-angular-grids';
import { patientDataManager } from '../services/dataManager';
@Component({
selector: 'app-patient-grid',
standalone: true,
imports: [GridModule],
providers: [ToolbarService],
template: `
<ejs-grid
[dataSource]="dataManager"
[toolbar]="['Search']"
>
</ejs-grid>
`
})When searching is performed in the Grid, a request is sent to the server with the following payload.

Step 9: Enable sorting
The sorting feature allows ordering Grid records by clicking column headers. Enable sorting by setting the allowSorting property to true and injecting the SortService module into the providers property. Without the SortService module, clicking headers will not sort the data. Customize sorting behavior using the sortSettings property.
import { Component } from '@angular/core';
import { GridModule, SortService } from '@syncfusion/ej2-angular-grids';
import { patientDataManager } from '../services/dataManager';
@Component({
selector: 'app-patient-grid',
standalone: true,
imports: [GridModule],
providers: [SortService],
template: `
<ejs-grid
[dataSource]="dataManager"
[allowSorting]="true"
>
</ejs-grid>
`
})
export class PatientGridComponent {
public dataManager = patientDataManager;
}When sorting is performed in the Grid, a request is sent to the server with the following payload.

Step 10: Enable CRUD actions
Enabling editing functionality within the grid requires configuring the allowEditing, allowAdding, and allowDeleting properties within the editSettings to true.
Editing feature requires a primary key column for CRUD operations. To define the primary key, set isPrimaryKey to true in particular column.
The toolbar property of the Grid component specifies the items displayed in the grid’s toolbar. By adding items such as Edit, Add, Delete, Update, and Cancel to the toolbar property, the corresponding edit options become available in the toolbar.
import { Component } from '@angular/core';
import { GridModule, EditService, ToolbarService } from '@syncfusion/ej2-angular-grids';
import { patientDataManager } from '../services/dataManager';
@Component({
selector: 'app-patient-grid',
standalone: true,
imports: [GridModule],
providers: [EditService, ToolbarService],
template: `
<ejs-grid
[dataSource]="dataManager"
[editSettings]="{ allowAdding: true, allowEditing: true, allowDeleting: true, mode: 'Dialog' }"
[toolbar]="['Add', 'Edit', 'Delete', 'Update', 'Cancel']"
>
<e-columns>
<e-column field="PatientID" headerText="ID" type="number" isPrimaryKey="true" width="100"></e-column>
</e-columns>
</ejs-grid>
`
})When a new record added in the Grid, a request is sent to the server with the following payload.

When a record updated in the Grid, a request is sent to the server with the following payload.

When a record deleted in the Grid, a request is sent to the server with the following payload.

Run the application
Both the backend Express.js server and the frontend Angular application need to run simultaneously for the Grid to function properly.
Prerequisites for running:
Before starting the servers, the following must be available:
- Two terminal windows or tabs open (one for backend, one for frontend)
- Both server and client directories created and configured
- All npm packages installed in both directories
Step 1: Start the Express.js backend server
Open the first terminal and navigate to the server folder from the project root:
cd ej2-angular-grid-with-express-js/server
npm run devThe server will start on http://localhost:5000. The following console output should appear:
Patients endpoint: http://localhost:5000/api/patients.
Step 2: Start the Angular frontend application
Open a new terminal and navigate to the client folder from the project root:
cd ej2-angular-grid-with-express-js/client
ng serveThe Angular application will start on http://localhost:4200.
The complete folder structure is as follows:
ej2-angular-grid-with-express-js/
├── server/
│ ├── src/
│ │ ├── controllers/
│ │ │ └── patients.controller.ts
│ │ ├── routes/
│ │ │ └── patients.routes.ts
│ │ ├── utils/
│ │ │ └── data.ts
│ │ ├── types/
│ │ │ └── interface.ts
│ │ └── server.ts
│ ├── node_modules/
│ ├── package.json
│ ├── package-lock.json
│ └── tsconfig.json
│
└── client/
├── src/
│ ├── app/
│ │ ├── components/
│ │ │ └── patient-grid.component.ts
│ │ └── app.component.ts
│ ├── services/
│ │ └── dataManager.ts
│ ├── styles.css
│ └── main.ts
├── node_modules/
├── package.json
├── angular.json
├── tsconfig.json
└── tsconfig.app.json
Complete sample repository
For a complete working implementation, refer to the GitHub repository.
The repository contains the complete Express.js backend implementation with all controller methods, API routes, TypeScript interfaces, and data models, plus a ready-to-run Angular frontend application.