Skip to main content
Build an Angular application powered by the Publive CDS API.
Important: Use a backend proxy or Angular Universal (SSR) to protect your API credentials.

Setup

1. Create a Backend Proxy

Create a simple Express endpoint (or use any server framework):
// server/api/publive.js
const express = require('express');
const router = express.Router();

const BASE_URL = 'https://cds.thepublive.com/publisher/<PUBLISHER_ID>';
const headers = {
  'Authorization': `Basic ${Buffer.from(process.env.PUBLIVE_API_KEY + ':' + process.env.PUBLIVE_API_SECRET).toString('base64')}`,
};

router.get('/posts', async (req, res) => {
  const query = new URLSearchParams(req.query);
  const response = await fetch(`${BASE_URL}/posts/?${query}`, { headers });
  if (!response.ok) return res.status(response.status).json({ error: 'Failed to fetch posts' });
  const data = await response.json();
  res.json(data);
});

router.get('/post/:slug', async (req, res) => {
  const response = await fetch(`${BASE_URL}/post/${req.params.slug}/`, { headers });
  if (!response.ok) return res.status(response.status).json({ error: 'Failed to fetch post' });
  const data = await response.json();
  res.json(data);
});

router.get('/categories', async (req, res) => {
  const response = await fetch(`${BASE_URL}/categories/`, { headers });
  if (!response.ok) return res.status(response.status).json({ error: 'Failed to fetch categories' });
  const data = await response.json();
  res.json(data);
});

router.get('/navbar', async (req, res) => {
  const response = await fetch(`${BASE_URL}/navbar/`, { headers });
  if (!response.ok) return res.status(response.status).json({ error: 'Failed to fetch navbar' });
  const data = await response.json();
  res.json(data);
});

module.exports = router;

2. Create a Publive Service

See the Post Listing and Post Details API references for the full response shape.
// src/app/services/publive.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

export interface Category {
  id: number;
  name: string;
  slug: string;
}

export interface Tag {
  id: number;
  name: string;
  slug: string;
}

export interface Post {
  id: number;
  title: string;
  short_description: string;
  content_html: string;
  banner_url: string | null;
  absolute_url: string;
  primary_category: Category | null;
  tags: Tag[];
  formatted_first_published_at_datetime: string;
}

export interface ApiResponse<T> {
  data: T;
}

@Injectable({ providedIn: 'root' })
export class PubliveService {
  private apiUrl = '/api/publive';

  constructor(private http: HttpClient) {}

  getPosts(params: Record<string, string> = {}): Observable<Post[]> {
    return this.http.get<ApiResponse<Post[]>>(`${this.apiUrl}/posts`, { params }).pipe(
      map(res => res.data),
      catchError(err => throwError(() => new Error(err.message || 'Failed to fetch posts')))
    );
  }

  getPost(slug: string): Observable<Post> {
    return this.http.get<ApiResponse<Post>>(`${this.apiUrl}/post/${slug}`).pipe(
      map(res => res.data),
      catchError(err => throwError(() => new Error(err.message || 'Failed to fetch post')))
    );
  }

  getCategories(): Observable<Category[]> {
    return this.http.get<ApiResponse<Category[]>>(`${this.apiUrl}/categories`).pipe(
      map(res => res.data),
      catchError(err => throwError(() => new Error(err.message || 'Failed to fetch categories')))
    );
  }

  getNavbar(): Observable<any[]> {
    return this.http.get<ApiResponse<any[]>>(`${this.apiUrl}/navbar`).pipe(
      map(res => res.data),
      catchError(err => throwError(() => new Error(err.message || 'Failed to fetch navbar')))
    );
  }
}

3. Create a Post List Component

// src/app/components/post-list/post-list.component.ts
import { Component, OnInit } from '@angular/core';
import { PubliveService, Post } from '../../services/publive.service';

@Component({
  selector: 'app-post-list',
  template: `
    <div *ngIf="loading">Loading...</div>
    <div *ngIf="error">Error: {{ error }}</div>
    <article *ngFor="let post of posts">
      <img *ngIf="post.banner_url" [src]="post.banner_url" [alt]="post.title" />
      <h2>
        <a [routerLink]="post.absolute_url">{{ post.title }}</a>
      </h2>
      <p>{{ post.short_description }}</p>
      <span>{{ post.primary_category?.name }}</span>
    </article>
  `
})
export class PostListComponent implements OnInit {
  posts: Post[] = [];
  loading = true;
  error: string | null = null;

  constructor(private publive: PubliveService) {}

  ngOnInit() {
    this.publive.getPosts({ limit: '20' }).subscribe({
      next: posts => {
        this.posts = posts;
        this.loading = false;
      },
      error: err => {
        this.error = err.message;
        this.loading = false;
      }
    });
  }
}

4. Create a Post Detail Component

// src/app/components/post-detail/post-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { PubliveService, Post } from '../../services/publive.service';

@Component({
  selector: 'app-post-detail',
  template: `
    <div *ngIf="loading">Loading...</div>
    <div *ngIf="error">Error: {{ error }}</div>
    <article *ngIf="post">
      <h1>{{ post.title }}</h1>
      <div class="meta">
        <span>{{ post.primary_category?.name }}</span>
        <time>{{ post.formatted_first_published_at_datetime }}</time>
      </div>
      <div [innerHTML]="post.content_html"></div>
    </article>
  `
})
export class PostDetailComponent implements OnInit {
  post: Post | null = null;
  loading = true;
  error: string | null = null;

  constructor(
    private route: ActivatedRoute,
    private publive: PubliveService
  ) {}

  ngOnInit() {
    const slug = this.route.snapshot.paramMap.get('slug');
    this.publive.getPost(slug!).subscribe({
      next: post => {
        this.post = post;
        this.loading = false;
      },
      error: err => {
        this.error = err.message;
        this.loading = false;
      }
    });
  }
}

Advanced

Filtering by Category

See the Category Listing API.
// Fetch articles in the "News" category (ID: 100)
this.publive.getPosts({ 'categories.id__eq': '100', 'type__eq': 'Article' })
  .subscribe(posts => this.posts = posts);

Building Navigation

See the Navbar API.
// src/app/components/navbar/navbar.component.ts
import { Component, OnInit } from '@angular/core';
import { PubliveService } from '../../services/publive.service';

@Component({
  selector: 'app-navbar',
  template: `
    <nav>
      <div *ngFor="let item of items">
        <a [href]="item.link">{{ item.name }}</a>
        <div *ngIf="item.children" class="submenu">
          <a *ngFor="let child of item.children" [href]="child.link">{{ child.name }}</a>
        </div>
      </div>
    </nav>
  `
})
export class NavbarComponent implements OnInit {
  items: any[] = [];

  constructor(private publive: PubliveService) {}

  ngOnInit() {
    this.publive.getNavbar().subscribe(items => this.items = items);
  }
}