REST API Integration Example

Learn how to integrate Now.js with external REST APIs using real-world examples

Live Demo

This example demonstrates fetching and displaying user data from the JSONPlaceholder API.

Users

Implementation

<!-- User List Component -->
<div data-component="user-list">
  <div class="toolbar">
    <input type="search" data-action="search">
    <button data-action="refresh">Refresh</button>
  </div>

  <div class="loading-indicator">
    <div class="spinner"></div>
    <span>Loading users...</span>
  </div>

  <div class="users-grid"></div>

  <div class="pagination">
    <button data-action="prev">Previous</button>
    <span class="page-info">Page <span class="current-page">1</span></span>
    <button data-action="next">Next</button>
  </div>
</div>

<!-- User Details Component -->
<div data-component="user-detail">
  <div class="user-info"></div>
  <div class="user-posts">
    <h4>Recent Posts</h4>
    <div class="posts-list"></div>
  </div>
</div>
// User List Component
Now.component('user-list', {
  state: {
    users: [],
    loading: false,
    error: null,
    page: 1,
    search: '',
    perPage: 6
  },

  async created() {
    await this.loadUsers();
  },

  methods: {
    async loadUsers() {
      this.state.loading = true;
      this.state.error = null;

      try {
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/users?_page=${this.state.page}`
        );

        if (!response.ok) throw new Error('Failed to load users');

        const users = await response.json();
        this.state.users = this.filterUsers(users);

      } catch (error) {
        this.state.error = error;
        NotificationManager.error('Failed to load users');
      } finally {
        this.state.loading = false;
      }
    },

    filterUsers(users) {
      if (!this.state.search) return users;

      const search = this.state.search.toLowerCase();
      return users.filter(user =>
        user.name.toLowerCase().includes(search) ||
        user.email.toLowerCase().includes(search)
      );
    },

    async refresh() {
      await this.loadUsers();
      NotificationManager.success('Users refreshed');
    },

    search(event) {
      this.state.search = event.target.value;
      this.state.page = 1;
      this.loadUsers();
    },

    async changePage(direction) {
      this.state.page += direction;
      await this.loadUsers();
    },

    showUserDetail(userId) {
      const userDetail = this.element.querySelector('[data-component="user-detail"]');
      userDetail.setAttribute('data-user-id', userId);
      EventManager.emit('user:selected', { userId });
    }
  }
});

// User Detail Component
Now.component('user-detail', {
  state: {
    user: null,
    posts: [],
    loading: false
  },

  created() {
    EventManager.on('user:selected', this.loadUserData.bind(this));
  },

  methods: {
    async loadUserData({ userId }) {
      this.state.loading = true;

      try {
        // Load user details
        const userResponse = await fetch(
          `https://jsonplaceholder.typicode.com/users/${userId}`
        );
        this.state.user = await userResponse.json();

        // Load user posts
        const postsResponse = await fetch(
          `https://jsonplaceholder.typicode.com/posts?userId=${userId}`
        );
        this.state.posts = await postsResponse.json();

      } catch (error) {
        NotificationManager.error('Failed to load user details');
      } finally {
        this.state.loading = false;
      }
    },

    back() {
      this.state.user = null;
      this.state.posts = [];
      this.element.style.display = 'none';
    }
  }
});
.demo-app {
  background: var(--color-surface);
  border-radius: var(--border-radius-lg);
  padding: var(--spacing-4);
  margin-top: var(--spacing-4);
}

.toolbar {
  display: flex;
  gap: var(--spacing-4);
  margin-bottom: var(--spacing-4);
}

.users-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: var(--spacing-4);
  margin: var(--spacing-4) 0;
}

.user-card {
  background: var(--color-background);
  border-radius: var(--border-radius);
  padding: var(--spacing-4);
  cursor: pointer;
  transition: all 0.2s;
}

.user-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-4);
  margin-top: var(--spacing-4);
}

.loading-indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-2);
  padding: var(--spacing-4);
}

.error-message {
  color: var(--color-error);
  padding: var(--spacing-4);
  text-align: center;
}
.error-message {
  font-size: var(--font-size-lg);
  color: var(--color-text-light);
  margin-bottom: var(--spacing-6);
}
/* User Details */
.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--spacing-4);
}

.user-info {
  background: var(--color-background);
  border-radius: var(--border-radius);
  padding: var(--spacing-4);
  margin-bottom: var(--spacing-4);
}

.posts-list {
  display: grid;
  gap: var(--spacing-4);
}

.post-card {
  background: var(--color-background);
  border-radius: var(--border-radius);
  padding: var(--spacing-4);
}

Key Features

Async/Await

Modern async/await syntax for clean API calls

Search & Filter

Real-time search with client-side filtering

Pagination

Server-side pagination support

Error Handling

Comprehensive error handling with notifications

Loading States

Loading indicators for better UX

Components

Modular component-based architecture

Best Practices

Error Handling

Always handle API errors gracefully and provide user feedback through notifications.

try {
  const response = await fetch(url);
  if (!response.ok) throw new Error('API Error');
  return await response.json();
} catch (error) {
  NotificationManager.error(error.message);
  throw error;
}

Loading States

Show loading indicators during API calls to improve user experience.

async loadData() {
  this.state.loading = true;
  try {
    await this.fetchData();
  } finally {
    this.state.loading = false;
  }
}

Component Communication

Use events for component communication to maintain loose coupling.

// Emit event
EventManager.emit('user:selected', { userId });

// Listen for event
EventManager.on('user:selected', this.loadUserData);