Node.js  

Building a Full Stack App with Node.js and Bun in 2025

The JavaScript ecosystem is evolving at lightning speed. In 2025, full stack development continues to leverage Node.js as a stable and mature backend runtime. But new tools like Bun have emerged, promising extreme performance, faster bundling, and modern features for web applications.

In this article, we will explore how to build a full stack application using Node.js and Bun, covering architecture, tooling, backend and frontend integration, performance optimization, and production-ready best practices. The focus is on practical implementation with real-world considerations, making it highly relevant for senior developers.

Why Node.js + Bun in 2025?

Node.js has remained the cornerstone of backend JavaScript for years, with a rich ecosystem, stable tooling, and enterprise support. However, Bun is gaining attention for the following reasons:

  1. Performance: Bun's runtime and bundler are built in Zig, offering faster startup times and native performance for server-side apps.

  2. Bundling & Transpiling: Bun integrates bundling and transpiling in a single step, reducing build complexity.

  3. Modern APIs: Bun provides native support for fetch, WebSocket, and other modern web standards without extra packages.

  4. Full Stack Integration: With Bun, you can run server, API, and frontend code in a single environment, simplifying development.

By combining Node.js for stability and Bun for speed, developers get the best of both worlds: a mature ecosystem with bleeding-edge performance.

Step 1: Setting Up the Development Environment

Installing Node.js

Node.js is required for the existing ecosystem and npm package support:

# macOS/Linux
brew install node

# Windows (using nvm)
nvm install --lts

Verify installation:

node -v
npm -v

Installing Bun

Bun can be installed via a simple script:

curl -fsSL https://bun.sh/install | bash

Verify installation:

bun -v

Project Structure

A typical full stack app structure:

my-fullstack-app/
│
├─ backend/           # Node.js/Bun backend
│  ├─ controllers/
│  ├─ models/
│  ├─ routes/
│  ├─ app.js
│
├─ frontend/          # Angular / React / Vue frontend
│  ├─ src/
│  ├─ angular.json
│
├─ package.json
└─ bun.lockb

Keeping backend and frontend separated ensures modularity and easier CI/CD.

Step 2: Backend with Node.js + Bun

Node.js provides a stable environment for APIs, while Bun speeds up startup, native module loading, and bundling.

1. Creating the Backend Entry Point

// backend/app.js
import express from "express";

const app = express();
app.use(express.json());

app.get("/api/hello", (req, res) => {
  res.json({ message: "Hello from Node + Bun backend!" });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Run with Node.js:

node backend/app.js

Run with Bun:

bun backend/app.js

2. Setting Up Routing and Controllers

Example: A simple REST API for tasks

// backend/routes/taskRoutes.js
import express from "express";
import { getTasks, addTask } from "../controllers/taskController.js";

const router = express.Router();

router.get("/", getTasks);
router.post("/", addTask);

export default router;
// backend/controllers/taskController.js
let tasks = [];

export const getTasks = (req, res) => res.json(tasks);

export const addTask = (req, res) => {
  const { title } = req.body;
  const newTask = { id: tasks.length + 1, title };
  tasks.push(newTask);
  res.status(201).json(newTask);
};

Integrate routes in app.js:

import taskRoutes from "./routes/taskRoutes.js";

app.use("/api/tasks", taskRoutes);

3. Database Integration

In 2025, Postgres, MySQL, MongoDB, and SQLite are commonly used. Using Prisma makes integration easier.

bun add prisma @prisma/client
npx prisma init

Example schema:

model Task {
  id    Int    @id @default(autoincrement())
  title String
  done  Boolean @default(false)
}

Generate client:

npx prisma generate

Backend API using Prisma:

import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

export const getTasks = async (req, res) => {
  const tasks = await prisma.task.findMany();
  res.json(tasks);
};

Step 3: Frontend with Angular

Angular remains a popular choice in 2025 for enterprise-grade SPAs.

1. Initialize Angular Project

ng new frontend --routing --style=scss
cd frontend
ng serve

The Angular app will be served on http://localhost:4200.

2. Connecting Angular to Node + Bun Backend

Use Angular's HttpClientModule for API requests:

// frontend/src/app/services/task.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Task {
  id: number;
  title: string;
  done: boolean;
}

@Injectable({ providedIn: 'root' })
export class TaskService {
  private apiUrl = 'http://localhost:3000/api/tasks';

  constructor(private http: HttpClient) {}

  getTasks(): Observable<Task[]> {
    return this.http.get<Task[]>(this.apiUrl);
  }

  addTask(title: string): Observable<Task> {
    return this.http.post<Task>(this.apiUrl, { title });
  }
}

3. Displaying Tasks in Angular Component

// frontend/src/app/components/task/task.component.ts
import { Component, OnInit } from '@angular/core';
import { TaskService, Task } from '../../services/task.service';

@Component({
  selector: 'app-task',
  templateUrl: './task.component.html',
  styleUrls: ['./task.component.scss']
})
export class TaskComponent implements OnInit {
  tasks: Task[] = [];
  newTask = '';

  constructor(private taskService: TaskService) {}

  ngOnInit() {
    this.loadTasks();
  }

  loadTasks() {
    this.taskService.getTasks().subscribe(tasks => this.tasks = tasks);
  }

  addTask() {
    if (this.newTask.trim()) {
      this.taskService.addTask(this.newTask).subscribe(task => {
        this.tasks.push(task);
        this.newTask = '';
      });
    }
  }
}
<!-- frontend/src/app/components/task/task.component.html -->
<div class="task-wrapper">
  <input [(ngModel)]="newTask" placeholder="Enter task" />
  <button (click)="addTask()">Add Task</button>

  <ul>
    <li *ngFor="let task of tasks">{{ task.title }}</li>
  </ul>
</div>

Step 4: Running Full Stack App

  1. Start backend (Node.js or Bun):

bun backend/app.js
  1. Start frontend:

ng serve
  1. Open http://localhost:4200 and test adding tasks.

The Angular frontend calls the Node/Bun backend, which serves REST APIs.

Step 5: Production Best Practices in 2025

1. Use Environment Variables

const PORT = process.env.PORT || 3000;
const DB_URL = process.env.DATABASE_URL;

Never hardcode secrets.

2. Performance Optimization

  • Bun improves server startup and module load.

  • Enable HTTP/2 or Fastify if using Node.js backend.

  • Use caching (Redis, CDN).

  • Use Angular's lazy loading for modules and ng build --prod.

3. Security Best Practices

  • Use Helmet.js for HTTP headers.

  • Validate all inputs.

  • Use CORS properly.

  • Enable HTTPS in production.

4. Logging and Monitoring

  • Bun has native performance metrics.

  • Use Winston or Pino in Node.js backend.

  • Monitor Angular frontend with tools like Sentry.

5. CI/CD Pipeline

  • Build frontend (ng build --prod) and backend (Bun bundle or Node.js dist).

  • Deploy on cloud platforms: AWS, GCP, Vercel, or Bun.sh server.

  • Use Bun's bundled artifacts for faster deployment.

Step 6: Advanced Patterns

1. Full Stack Realtime with WebSockets

Bun natively supports WebSocket APIs:

const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', ws => {
  ws.on('message', message => console.log(message.toString()));
});

Integrate Angular with RxJS for reactive updates.

2. Monorepo Architecture

  • Use Nx or Turborepo to manage frontend + backend in a single repository.

  • Shared libraries for models and interfaces.

  • Faster cross-team development.

3. Server-Side Rendering (Angular + Bun)

  • Use Angular Universal to pre-render pages.

  • Serve static pages via Bun for extreme speed.

  • Improves SEO and perceived performance.

4. Bun + Node Hybrid

  • Bun for dev, testing, and fast bundling.

  • Node.js for stable production environment with long-term LTS support.

  • This approach combines speed and stability.

Conclusion

In 2025, building full stack apps requires a careful balance between performance, developer experience, and ecosystem stability. Node.js provides maturity and wide library support, while Bun offers unmatched speed and modern APIs. Together, they enable:

  • Fast development and bundling

  • High-performance backend

  • Seamless frontend integration

  • Production-ready reliability

Senior developers can leverage Node.js + Bun to build scalable, maintainable, and future-proof applications. Whether you are building dashboards, SaaS platforms, or enterprise apps, this combination brings both speed and reliability to the full stack.