GitHub

Services Layer

How to organize all backend communication in one place — so when an API changes, you fix it in one file, not twenty.

01

Principle

When you fetch data directly inside a component, the component becomes responsible for knowing the URL, the HTTP method, the request format, and the error handling. That is four responsibilities too many. A services layer centralizes all backend communication — components just call a function and get data back. When the API changes, you fix it in one file, not twenty.

lightbulb

A service function should read like plain English: getUserById(id), createOrder(data), deletePost(id). If it needs more than one argument object, consider splitting it into two functions.

02

Rules

  • check_circle
    Services only talk to the APIA service function takes inputs, calls the API, and returns data. It does not touch state, does not render anything, and does not know about React.
  • check_circle
    One file per resourceGroup service functions by the API resource they belong to: users.ts, orders.ts, recipes.ts. Not by HTTP method.
  • check_circle
    Services live in lib/The services layer belongs in src/lib/ alongside the API client and query keys — not inside a feature folder.
  • check_circle
    Hooks consume services, components consume hooksComponents never call service functions directly. The chain is: service → custom hook → component.
03

Pattern

lib/services/users.ts — service layer pattern
import { api } from '@/lib/api';
import { ENDPOINTS } from '@/lib/endpoints';
import type { User, UsersResponse, CreateUserInput, UpdateUserInput } from '@/shared/types/user';

// ✅ Service functions — pure API communication (no React, no state)
export const usersService = {
  getAll: (params?: { limit?: number; skip?: number }): Promise<UsersResponse> =>
    api.get<UsersResponse>(ENDPOINTS.users.list, { params }),

  getById: (id: number): Promise<User> =>
    api.get<User>(ENDPOINTS.users.detail(id)),

  create: (data: CreateUserInput): Promise<User> =>
    api.post<User>(ENDPOINTS.users.create, data),

  update: (id: number, data: UpdateUserInput): Promise<User> =>
    api.put<User>(ENDPOINTS.users.update(id), data),

  delete: (id: number): Promise<User> =>
    api.delete<User>(ENDPOINTS.users.delete(id)),
};
04

Implementation

info

Version Compatibility

Requires React 19+ and the latest stable versions of all dependencies shown.

In Next.js App Router, service functions can be called directly in Server Components. For Client Components, wrap them in React Query hooks. See the full chain in the starter: github.com/sindev08/react-principles-nextjs → src/lib/

lib/services/users.ts — from react-principles-nextjs starter
// lib/api-client.ts — fetch-based factory (NOT axios)
import { createApiClient } from './api-client';
export const api = createApiClient({
  baseUrl: process.env.NEXT_PUBLIC_API_URL ?? 'https://dummyjson.com',
});

// lib/services/users.ts — pure API communication
import { api } from '@/lib/api';
import { ENDPOINTS } from '@/lib/endpoints';
import type { User, UsersResponse } from '@/shared/types/user';

export const usersService = {
  getAll: (params?: { limit?: number; skip?: number }): Promise<UsersResponse> =>
    api.get<UsersResponse>(ENDPOINTS.users.list, { params }),
  getById: (id: number): Promise<User> =>
    api.get<User>(ENDPOINTS.users.detail(id)),
  search: (q: string): Promise<UsersResponse> =>
    api.get<UsersResponse>(ENDPOINTS.users.search, { params: { q } }),
};

// features/users/hooks/useUsers.ts — hook wraps service with React Query
'use client';
import { useQuery } from '@tanstack/react-query';
import { queryKeys } from '@/lib/query-keys';

export function useUsers(params?: { limit?: number; skip?: number }) {
  return useQuery({
    queryKey: queryKeys.users.list(params ?? {}),
    queryFn: () => usersService.getAll(params),
  });
}
open_in_new

View services layer in starter

View the real implementation in react-principles-nextjs

arrow_forward
menu_book
React Patterns

Helping developers build robust React applications since 2026.

© 2026 React Patterns Cookbook. Built with ❤️ for the community.
react-principles