cd..blog

Angular & PocketBase: Full-Stack Synergy for Modern Web Apps

const published = "Jan 3, 2026, 10:21 PM";const readTime = 5 min;
angularpocketbasetypescriptfullstackwebdev
Build dynamic web apps with Angular's frontend power and PocketBase's lightweight backend. Learn authentication, data management, and best practices.

Angular & PocketBase: Full-Stack Synergy for Modern Web Apps

Modern web development often demands a robust frontend paired with an efficient, scalable backend. While traditional full-stack setups can be complex, integrating Angular with PocketBase offers a streamlined, powerful alternative. Angular provides a comprehensive framework for building single-page applications, while PocketBase delivers a full-featured backend (database, authentication, file storage, API) in a single, self-hosted Go executable.

This guide demonstrates how to combine these two technologies to create a dynamic web application, focusing on practical implementation with TypeScript.

1. PocketBase Backend Setup

First, set up your PocketBase instance. Download the appropriate executable from the official website and run it. PocketBase will automatically create a pb_data directory and start a server, typically on http://127.0.0.1:8090.

./pocketbase serve

Access the admin UI at http://127.0.0.1:8090/_/ to create your first collection. For this example, we'll create a "tasks" collection with the following fields:

  • title (text)
  • isComplete (boolean, default false)
  • user (relation, to users collection, single)

Ensure you set appropriate access rules for the tasks collection, allowing authenticated users to create, view, update, and delete their own tasks.

2. Angular Frontend Initialization

Create a new Angular project using the CLI. This sets up the project structure and necessary dependencies.

ng new pocketbase-angular-app --standalone --routing --style=scss
cd pocketbase-angular-app

3. Integrating the PocketBase Client

Install the PocketBase JavaScript SDK in your Angular project. This SDK provides a convenient way to interact with your PocketBase backend.

npm install pocketbase

Create an Angular service to encapsulate PocketBase interactions. This promotes reusability and testability.

// src/app/core/services/pocketbase.service.ts
import { Injectable, signal } from '@angular/core';
import PocketBase from 'pocketbase';
import { Router } from '@angular/router';
import { environment } from '../../../environments/environment';

export interface Task {
  id: string;
  title: string;
  isComplete: boolean;
  user: string;
  collectionId: string;
  collectionName: string;
  created: string;
  updated: string;
}

@Injectable({ providedIn: 'root' })
export class PocketbaseService {
  private pb = new PocketBase(environment.pocketbaseUrl);
  currentUser = signal<any | null>(this.pb.authStore.model);

  constructor(private router: Router) {
    // React to auth store changes
    this.pb.authStore.onChange(() => {
      this.currentUser.set(this.pb.authStore.model);
      if (!this.pb.authStore.isValid) {
        this.router.navigate(['/login']);
      }
    }, true);
  }

  get client(): PocketBase {
    return this.pb;
  }

  isLoggedIn(): boolean {
    return this.pb.authStore.isValid;
  }

  async login(email: string, password: string): Promise<void> {
    await this.pb.collection('users').authWithPassword(email, password);
    this.router.navigate(['/tasks']);
  }

  async logout(): Promise<void> {
    this.pb.authStore.clear();
    this.router.navigate(['/login']);
  }

  async register(email: string, password: string): Promise<void> {
    await this.pb.collection('users').create({ email, password, passwordConfirm: password });
    await this.login(email, password);
  }

  async getTasks(): Promise<Task[]> {
    if (!this.pb.authStore.isValid) return [];
    return this.pb.collection('tasks').getFullList<Task>({
      filter: `user = "${this.pb.authStore.model?.id}"`,
      sort: '-created'
    });
  }

  async addTask(title: string): Promise<Task> {
    if (!this.pb.authStore.isValid) throw new Error('Not authenticated');
    return this.pb.collection('tasks').create<Task>({
      title,
      isComplete: false,
      user: this.pb.authStore.model?.id
    });
  }

  async updateTask(id: string, isComplete: boolean): Promise<Task> {
    if (!this.pb.authStore.isValid) throw new Error('Not authenticated');
    return this.pb.collection('tasks').update<Task>(id, { isComplete });
  }
}

Remember to define environment.pocketbaseUrl in src/environments/environment.ts (e.g., pocketbaseUrl: 'http://127.0.0.1:8090').

4. Authentication Flow

Create a login component (src/app/auth/login.component.ts) that leverages the PocketbaseService for user authentication. Angular's reactive forms are ideal for handling user input.

// src/app/auth/login.component.ts
import { Component } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { PocketbaseService } from '../core/services/pocketbase.service';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [ReactiveFormsModule, RouterLink],
  template: `
    <h2>Login</h2>
    <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
      <input type="email" formControlName="email" placeholder="Email" />
      <input type="password" formControlName="password" placeholder="Password" />
      <button type="submit" [disabled]="loginForm.invalid">Login</button>
    </form>
    <p>Don't have an account? <a routerLink="/register">Register here</a></p>
  `,
})
export class LoginComponent {
  loginForm = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', Validators.required],
  });

  constructor(private fb: FormBuilder, private pbService: PocketbaseService) {}

  async onSubmit(): Promise<void> {
    if (this.loginForm.valid) {
      const { email, password } = this.loginForm.value;
      try {
        await this.pbService.login(email!, password!); // Non-null assertion for simplicity
      } catch (error) {
        console.error('Login failed:', error);
        alert('Login failed. Please check your credentials.');
      }
    }
  }
}

A similar RegisterComponent can be created, calling this.pbService.register().

5. Building a Task Management Feature

Once authenticated, users should be able to manage their tasks. Create a TasksComponent that uses the PocketbaseService to fetch, add, and update tasks.

// src/app/tasks/tasks.component.ts
import { Component, OnInit } from '@angular/core';
import { PocketbaseService, Task } from '../core/services/pocketbase.service';
import { CommonModule } from '@angular/common';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';

@Component({
  selector: 'app-tasks',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  template: `
    <h2>My Tasks</h2>
    <form [formGroup]="taskForm" (ngSubmit)="addTask()">
      <input type="text" formControlName="title" placeholder="New task title" />
      <button type="submit" [disabled]="taskForm.invalid">Add Task</button>
    </form>

    <ul>
      <li *ngFor="let task of tasks">
        <input
          type="checkbox"
          [checked]="task.isComplete"
          (change)="toggleTaskCompletion(task.id, $event)"
        />
        <span [class.completed]="task.isComplete">{{ task.title }}</span>
      </li>
    </ul>
    <button (click)="pbService.logout()">Logout</button>
  `,
  styles: [`
    .completed { text-decoration: line-through; color: #888; }
  `]
})
export class TasksComponent implements OnInit {
  tasks: Task[] = [];
  taskForm = this.fb.group({
    title: ['', Validators.required],
  });

  constructor(public pbService: PocketbaseService, private fb: FormBuilder) {}

  ngOnInit(): void {
    this.loadTasks();
  }

  async loadTasks(): Promise<void> {
    try {
      this.tasks = await this.pbService.getTasks();
    } catch (error) {
      console.error('Failed to load tasks:', error);
    }
  }

  async addTask(): Promise<void> {
    if (this.taskForm.valid) {
      const title = this.taskForm.value.title!;
      try {
        const newTask = await this.pbService.addTask(title);
        this.tasks.unshift(newTask); // Add to the beginning
        this.taskForm.reset();
      } catch (error) {
        console.error('Failed to add task:', error);
      }
    }
  }

  async toggleTaskCompletion(taskId: string, event: Event): Promise<void> {
    const isComplete = (event.target as HTMLInputElement).checked;
    try {
      const updatedTask = await this.pbService.updateTask(taskId, isComplete);
      const index = this.tasks.findIndex(t => t.id === taskId);
      if (index !== -1) {
        this.tasks[index] = updatedTask;
      }
    } catch (error) {
      console.error('Failed to update task:', error);
    }
  }
}

Finally, configure your Angular router (src/app/app.routes.ts) to include these components and protect routes with a simple guard.

// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { LoginComponent } from './auth/login.component';
import { RegisterComponent } from './auth/register.component'; // Assume you created this
import { TasksComponent } from './tasks/tasks.component';
import { inject } from '@angular/core';
import { PocketbaseService } from './core/services/pocketbase.service';

const authGuard = () => {
  const pbService = inject(PocketbaseService);
  return pbService.isLoggedIn() ? true : pbService.router.createUrlTree(['/login']);
};

export const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: 'register', component: RegisterComponent },
  { path: 'tasks', component: TasksComponent, canActivate: [authGuard] },
  { path: '', redirectTo: '/tasks', pathMatch: 'full' },
  { path: '**', redirectTo: '/tasks' },
];

Conclusion

Integrating Angular with PocketBase offers an incredibly efficient way to develop full-stack applications. Angular's structured component-based architecture and PocketBase's all-in-one backend solution reduce boilerplate and accelerate development. This combination is particularly well-suited for prototypes, MVPs, and even production applications where a self-hosted, lightweight backend is preferred. Explore PocketBase's real-time subscriptions and file storage capabilities to further enhance your applications.