Creating a Full-Stack Application with Angular 19,
Express, [Link], and MongoDB using pnpm
This comprehensive guide walks you through creating a basic full-stack application that
retrieves data from MongoDB and displays it in an Angular 19 web application. We'll be using
pnpm as our package manager and setting up a monorepo structure to organize our codebase
effectively.
Setting Up the Monorepo Structure
Let's start by creating a monorepo structure using pnpm to manage both our frontend and
backend applications.
Prerequisites
First, ensure you have [Link] installed:
node -v
If [Link] isn't installed, download it from the official website. Next, install pnpm globally:
# Using npm
npm install -g pnpm
# Or using Brew if available
brew install pnpm
Verify the installation:
pnpm -v
Creating the Monorepo
1. Create a root directory for your project:
mkdir mean-fullstack-app
cd mean-fullstack-app
2. Initialize pnpm in the root directory:
pnpm init
3. Create a pnpm workspace configuration file:
# Create [Link] file
echo "packages:
- 'apps/*'
- 'packages/*'" > [Link]
4. Set up the directory structure:
mkdir -p apps/server apps/client packages/tsconfig
This will create a structure like this:
mean-fullstack-app/
├── apps/
│ ├── client/
│ └── server/
├── packages/
│ └── tsconfig/
├── [Link]
└── [Link]
This monorepo approach allows us to manage both the frontend and backend code within a
single repository while sharing common configurations and packages [1] .
Setting Up the Backend ([Link] and MongoDB)
Let's start with setting up the backend using [Link] and MongoDB.
Initialize the Server Project
1. Navigate to the server directory:
cd apps/server
2. Initialize a new [Link] file:
pnpm init
3. Open the generated [Link] and modify the main field to "src/[Link]" [1] .
Install Backend Dependencies
pnpm add express mongoose cors dotenv
pnpm add -D typescript @types/express @types/node @types/cors ts-node nodemon
Configure TypeScript
1. Create a [Link] file:
npx tsc --init
2. Update the [Link] with appropriate settings:
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"outDir": "./dist",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
3. Create a src directory for your server code:
mkdir src
Create the Basic Express Server
Create a file src/[Link]:
import express from 'express';
import mongoose from 'mongoose';
import cors from 'cors';
import dotenv from 'dotenv';
// Load environment variables
[Link]();
// Initialize Express app
const app = express();
const PORT = [Link] || 3000;
// Middleware
[Link](cors());
[Link]([Link]());
// MongoDB Connection
const MONGODB_URI = [Link].MONGODB_URI || 'mongodb://localhost:27017/meanapp';
[Link](MONGODB_URI)
.then(() => [Link]('Connected to MongoDB'))
.catch(err => [Link]('MongoDB connection error:', err));
// Basic route
[Link]('/api', (req, res) => {
[Link]({ message: 'Welcome to the MEAN Stack API' });
});
// Start server
[Link](PORT, () => {
[Link](`Server running on port ${PORT}`);
});
Create MongoDB Model and Routes
1. Create a models directory:
mkdir src/models
2. Create a model for your data (e.g., src/models/[Link]):
import mongoose, { Schema, Document } from 'mongoose';
export interface IItem extends Document {
name: string;
description: string;
createdAt: Date;
}
const ItemSchema: Schema = new Schema({
name: { type: String, required: true },
description: { type: String, required: true },
createdAt: { type: Date, default: [Link] }
});
export default [Link]<IItem>('Item', ItemSchema);
3. Create a routes directory:
mkdir src/routes
4. Create routes for CRUD operations (e.g., src/routes/[Link]):
import express, { Router } from 'express';
import Item from '../models/item';
const router: Router = [Link]();
// Get all items
[Link]('/', async (req, res) => {
try {
const items = await [Link]();
[Link](items);
} catch (err) {
[Link](500).json({ message: 'Server error' });
}
});
// Create a new item
[Link]('/', async (req, res) => {
try {
const newItem = new Item({
name: [Link],
description: [Link]
});
const savedItem = await [Link]();
[Link](201).json(savedItem);
} catch (err) {
[Link](400).json({ message: 'Invalid data' });
}
});
export default router;
5. Update your src/[Link] to include the routes:
// ... other imports
import itemRoutes from './routes/items';
// ... middleware setup
// Routes
[Link]('/api/items', itemRoutes);
// ... server startup
6. Add npm scripts to [Link]:
"scripts": {
"dev": "nodemon src/[Link]",
"build": "tsc",
"start": "node dist/[Link]"
}
Create Environment Variables
Create a .env file in the server directory:
PORT=3000
MONGODB_URI=mongodb://localhost:27017/meanapp
This completes the backend setup. The server is now configured to connect to MongoDB and
provide API endpoints for items [2] [3] .
Setting Up the Angular 19 Frontend
Now, let's set up the Angular 19 frontend application.
Create a New Angular Project
1. Navigate to the client directory:
cd ../../apps/client
2. Install Angular CLI globally:
pnpm add -g @angular/cli
3. Create a new Angular application:
ng new client --standalone --routing --style=scss
cd client
Configure Proxy for API Communication
Create a [Link] file in the client directory:
{
"/api": {
"target": "[Link]
"secure": false,
"logLevel": "debug"
}
}
Update the [Link] file to use the proxy configuration:
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"proxyConfig": "[Link]"
},
// ... other config
}
Create Service to Fetch Data
1. Generate a service to communicate with the API:
ng generate service services/item
2. Update the service (src/app/services/[Link]):
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface Item {
_id: string;
name: string;
description: string;
createdAt: Date;
}
@Injectable({
providedIn: 'root'
})
export class ItemService {
private apiUrl = '/api/items';
constructor(private http: HttpClient) { }
getItems(): Observable<Item[]> {
return [Link]<Item[]>([Link]);
}
addItem(item: { name: string; description: string }): Observable<Item> {
return [Link]<Item>([Link], item);
}
}
Create Components to Display Data
1. Generate a component to display the items:
ng generate component components/item-list
2. Update the component (src/app/components/item-list/[Link]):
import { Component, OnInit } from '@angular/core';
import { ItemService, Item } from '../../services/[Link]';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-item-list',
standalone: true,
imports: [CommonModule],
templateUrl: './[Link]',
styleUrls: ['./[Link]']
})
export class ItemListComponent implements OnInit {
items: Item[] = [];
loading = true;
error: string | null = null;
constructor(private itemService: ItemService) { }
ngOnInit(): void {
[Link]();
}
fetchItems(): void {
[Link]().subscribe({
next: (data) => {
[Link] = data;
[Link] = false;
},
error: (err) => {
[Link] = 'Failed to load items';
[Link] = false;
[Link](err);
}
});
}
}
3. Update the template file (src/app/components/item-list/[Link]):
<div class="container">
<h2>Items List</h2>
<div *ngIf="loading">Loading...</div>
<div *ngIf="error" class="error">{{ error }}</div>
<div *ngIf="!loading && !error">
<div *ngIf="[Link] === 0">No items found.</div>
<div *ngIf="[Link] > 0" class="items-grid">
<div *ngFor="let item of items" class="item-card">
<h3>{{ [Link] }}</h3>
<p>{{ [Link] }}</p>
<small>Created: {{ [Link] | date }}</small>
</div>
</div>
</div>
</div>
4. Add some basic styling (src/app/components/item-list/[Link]):
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.items-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.item-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.error {
color: red;
font-weight: bold;
}
Update the App Module to Include HTTP Client
Update src/app/[Link] to include HttpClientModule:
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './[Link]';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient()
]
};
Update the App Routes
Update src/app/[Link] to include the ItemListComponent:
import { Routes } from '@angular/router';
import { ItemListComponent } from './components/item-list/[Link]';
export const routes: Routes = [
{ path: '', redirectTo: 'items', pathMatch: 'full' },
{ path: 'items', component: ItemListComponent }
];
Update App Component
Update src/app/[Link]:
<header>
<h1>MEAN Stack Application</h1>
</header>
<main>
<router-outlet></router-outlet>
</main>
<footer>
<p>© 2025 MEAN Stack Demo</p>
</footer>
This completes the Angular frontend setup [4] [5] .
Creating a Form to Add New Items
Let's enhance our application by adding a form to create new items.
Create an Item Form Component
1. Generate a new component:
ng generate component components/item-form
2. Update the component (src/app/components/item-form/[Link]):
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { ItemService } from '../../services/[Link]';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-item-form',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
templateUrl: './[Link]',
styleUrls: ['./[Link]']
})
export class ItemFormComponent {
itemForm: FormGroup;
submitting = false;
error: string | null = null;
success = false;
constructor(
private fb: FormBuilder,
private itemService: ItemService
) {
[Link] = [Link]({
name: ['', [[Link], [Link](3)]],
description: ['', [[Link], [Link](5)]]
});
}
onSubmit(): void {
if ([Link]) {
return;
}
[Link] = true;
[Link] = null;
[Link] = false;
[Link]([Link]).subscribe({
next: () => {
[Link] = false;
[Link] = true;
[Link]();
},
error: (err) => {
[Link] = false;
[Link] = 'Failed to add item';
[Link](err);
}
});
}
}
3. Create the template (src/app/components/item-form/[Link]):
<div class="form-container">
<h2>Add New Item</h2>
<form [formGroup]="itemForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" formControlName="name">
<div *ngIf="[Link]('name')?.invalid && [Link]('name')?.touched" class="
Name is required and must be at least 3 characters long.
</div>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea id="description" formControlName="description" rows="4"></textarea>
<div *ngIf="[Link]('description')?.invalid && [Link]('description')?.to
Description is required and must be at least 5 characters long.
</div>
</div>
<button type="submit" [disabled]="[Link] || submitting">
{{ submitting ? 'Adding...' : 'Add Item' }}
</button>
</form>
<div *ngIf="error" class="error-message">{{ error }}</div>
<div *ngIf="success" class="success-message">Item added successfully!</div>
</div>
4. Add some styling (src/app/components/item-form/[Link]):
.form-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.error {
color: red;
font-size: 12px;
margin-top: 5px;
}
.error-message {
color: red;
margin-top: 20px;
}
.success-message {
color: green;
margin-top: 20px;
}
5. Update src/app/[Link] to include the form component:
import { Routes } from '@angular/router';
import { ItemListComponent } from './components/item-list/[Link]';
import { ItemFormComponent } from './components/item-form/[Link]';
export const routes: Routes = [
{ path: '', redirectTo: 'items', pathMatch: 'full' },
{ path: 'items', component: ItemListComponent },
{ path: 'add-item', component: ItemFormComponent }
];
6. Update the app component (src/app/[Link]) to include navigation:
<header>
<h1>MEAN Stack Application</h1>
<nav>
<a routerLink="/items">View Items</a>
<a routerLink="/add-item">Add Item</a>
</nav>
</header>
<main>
<router-outlet></router-outlet>
</main>
<footer>
<p>© 2025 MEAN Stack Demo</p>
</footer>
Setting Up Scripts for the Entire Project
Let's update the root [Link] to include scripts that run both the frontend and backend:
{
"name": "mean-fullstack-app",
"version": "1.0.0",
"scripts": {
"start:server": "cd apps/server && pnpm run dev",
"start:client": "cd apps/client && ng serve",
"dev": "concurrently \"pnpm run start:server\" \"pnpm run start:client\"",
"build:server": "cd apps/server && pnpm run build",
"build:client": "cd apps/client && ng build",
"build": "pnpm run build:server && pnpm run build:client"
},
"devDependencies": {
"concurrently": "^8.2.0"
}
}
Install the concurrently package:
pnpm add -D concurrently
Running the Application
1. Start MongoDB:
If using MongoDB locally, make sure your MongoDB server is running.
2. From the root directory, start both the frontend and backend:
pnpm run dev
This will concurrently run the [Link] backend server and the Angular frontend development
server.
3. Open your browser and navigate to:
Frontend: [Link]
Backend API: [Link]
Conclusion
You've successfully created a full-stack MEAN application using pnpm as the package
manager. The application:
1. Uses a monorepo structure to organize frontend and backend code
2. Has a [Link]/[Link] backend connected to MongoDB
3. Features an Angular 19 frontend that fetches and displays data from the API
4. Includes a form to add new items to the database
This basic application provides a foundation that you can extend with more features, such as
authentication, additional CRUD operations, and more complex data models. The monorepo
approach makes it easier to share code between frontend and backend, improving
maintainability and development workflow.
By following these steps, you've created a modern full-stack JavaScript application that
demonstrates the power and flexibility of the MEAN stack with the efficiency of pnpm for
package management.
⁂
Understanding the Missing [Link] in
Modern Angular Projects
If you've noticed that the [Link] file is missing from your Angular project's src/app folder,
you're encountering one of the significant changes introduced in recent versions of Angular. This
isn't an error – it's by design in newer Angular applications.
Why [Link] is Missing
Starting from Angular v17 (released in November 2023), the Angular team made "Standalone
Components" the new default for applications created with the Angular CLI. This means:
New Angular projects no longer generate the traditional [Link] file automatically
The application structure now relies on standalone components that don't require NgModule
declarations
This change was implemented to simplify Angular development by reducing boilerplate
code [6] [7]
Your Options for Handling This Situation
Option 1: Embrace Standalone Components (Recommended)
The Angular team strongly recommends using standalone components as they are easier to
understand, require less boilerplate, and represent the future direction of Angular development.
With standalone components, you directly import what you need in each component rather than
declaring everything in modules [6] .
Option 2: Create a New Project with NgModules
If you prefer or require the traditional NgModule approach (perhaps for compatibility with
existing code or tutorials):
ng new your-project-name --no-standalone
This command will generate a new project with the traditional module-based structure, including
[Link] [7] [8] .
Option 3: Manually Add [Link] to Your Existing Project
If you need to add an [Link] file to your existing standalone project:
1. Create a new file named [Link] in your src/app directory
2. Add the following basic structure (customize as needed) [6] :
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './[Link]';
@NgModule({
declarations: [
AppComponent
// Add other components here
],
imports: [
BrowserModule,
// Add other modules here
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
3. Update your [Link] file to use this module instead of the standalone bootstrap
Understanding Angular's Evolution
This change reflects Angular's evolution toward a simpler component model. The standalone
approach allows developers to:
Directly import dependencies where they're needed
Avoid the cognitive overhead of managing NgModules
Benefit from better tree-shaking for smaller bundle sizes
Create more portable, self-contained components [6] [9]
Conclusion
The missing [Link] file is not an issue but a reflection of Angular's move toward a more
streamlined development experience. You can either adapt to this new approach (recommended
for new projects) or use the --no-standalone flag when creating new projects if you prefer the
traditional module-based structure.
If you're following older tutorials or courses that reference [Link], keep in mind they were
likely created before this change in Angular 17, and you'll need to make appropriate adjustments
or create a non-standalone project.
⁂
1. [Link]
2. [Link]
3. [Link]
4. [Link]
5. [Link]
6. [Link]
7. [Link]
er0df
8. [Link]
9. [Link]