REST API Best Practices - Kompletny Przewodnik dla Developerów 2026

Sławomir Plamowski 15 min czytania
api-design backend express http nestjs nodejs pytania-rekrutacyjne rest-api

Projektowanie REST API to nie jest tylko "napisz endpoint". To sztuka balansowania między prostotą dla klientów, wydajnością, bezpieczeństwem i możliwością ewolucji. Źle zaprojektowane API staje się długiem technicznym - trudne do zmiany, źle udokumentowane, nieintuicyjne dla developerów.

Ten przewodnik to kompletne kompendium REST API best practices - od podstaw przez metody HTTP, kody statusu, paginację, autentykację, po optymalizację wydajności i dokumentację. Wszystko co rekruterzy sprawdzają na rozmowach backend.

Podstawy REST - Architektura i Zasady

REST (Representational State Transfer) to styl architektoniczny zdefiniowany przez Roya Fieldinga w 2000 roku. Nie jest to protokół ani standard - to zbiór ograniczeń (constraints), które nadają API określone właściwości.

6 Zasad REST

1. Client-Server - oddzielenie klienta od serwera. Klient nie musi wiedzieć jak działa backend, serwer nie musi wiedzieć jaki jest frontend. Pozwala na niezależny rozwój obu stron.

2. Stateless - każdy request musi zawierać wszystkie informacje potrzebne do jego przetworzenia. Serwer nie przechowuje stanu sesji klienta między requestami. Ułatwia skalowanie (każdy serwer może obsłużyć każdy request).

3. Cacheable - odpowiedzi muszą definiować czy mogą być cache'owane. Poprawia wydajność i skalowalność.

4. Uniform Interface - spójny interfejs oparty na zasobach:

  • Identyfikacja zasobów (URI)
  • Manipulacja zasobów przez reprezentacje
  • Samo-opisujące się wiadomości
  • HATEOAS (Hypermedia as the Engine of Application State)

5. Layered System - możliwość dodania warstw pośrednich (load balancer, cache, gateway) bez wiedzy klienta.

6. Code on Demand (opcjonalne) - serwer może dostarczyć wykonywalny kod (np. JavaScript).

Richardson Maturity Model

Model określa poziomy dojrzałości REST API:

Poziom Nazwa Opis
0 The Swamp of POX Jeden endpoint, XML/JSON, RPC-style
1 Resources Wiele URI, jeden zasób per endpoint
2 HTTP Verbs Właściwe użycie GET, POST, PUT, DELETE
3 Hypermedia Controls HATEOAS - linki w response

Większość API zatrzymuje się na poziomie 2. Poziom 3 (HATEOAS) jest rzadko implementowany w praktyce.

REST vs SOAP vs GraphQL

REST:
+ Prostota, wykorzystuje HTTP
+ Cache-friendly
+ Szeroka adopcja
- Over-fetching/under-fetching
- Wiele roundtripów dla powiązanych danych

SOAP:
+ Wbudowane bezpieczeństwo (WS-Security)
+ Transakcje, kontrakty WSDL
- Verbose XML
- Większy overhead

GraphQL:
+ Jedno query = wszystkie dane
+ Typowany schemat
+ Brak over/under-fetching
- Złożoność cache'owania
- Trudniejsza optymalizacja

Metody HTTP - Semantyka i Idempotentność

Każda metoda HTTP ma określoną semantykę. Jej prawidłowe użycie to fundament REST API.

Metody CRUD

POST   /api/users          Create - tworzenie nowego zasobu
GET    /api/users          Read   - pobieranie listy zasobów
GET    /api/users/123      Read   - pobieranie pojedynczego zasobu
PUT    /api/users/123      Update - pełna aktualizacja zasobu
PATCH  /api/users/123      Update - częściowa aktualizacja
DELETE /api/users/123      Delete - usunięcie zasobu

PUT vs PATCH - Kluczowa Różnica

PUT - zastępuje CAŁY zasób:

// Aktualny stan zasobu
{
  "id": 123,
  "name": "Jan",
  "email": "jan@example.com",
  "role": "user"
}

// PUT /api/users/123
// Wysyłasz kompletny obiekt
{
  "name": "Jan Kowalski",
  "email": "jan@example.com",
  "role": "user"
}

// Wynik: pełna zamiana

PATCH - modyfikuje WYBRANE pola:

// PATCH /api/users/123
// Wysyłasz tylko to co chcesz zmienić
{
  "name": "Jan Kowalski"
}

// Wynik: tylko name się zmienia, reszta bez zmian

Kiedy który używać?

  • PUT: gdy masz pełny obiekt i chcesz go nadpisać
  • PATCH: gdy aktualizujesz pojedyncze pola

Idempotentność

Operacja jest idempotentna gdy wielokrotne jej wykonanie daje ten sam efekt co jednokrotne.

Metoda Idempotentna Bezpieczna Opis
GET Tak Tak Tylko odczyt
HEAD Tak Tak Jak GET ale bez body
OPTIONS Tak Tak Zwraca dozwolone metody
PUT Tak Nie Wielokrotne PUT daje ten sam stan
DELETE Tak Nie Drugie DELETE zwraca 404
POST Nie Nie Każde POST może tworzyć nowy zasób
PATCH Zależy Nie Może być idempotentne

Dlaczego to ważne? Klient może bezpiecznie retry'ować idempotentne operacje przy błędach sieci.

POST vs PUT do Tworzenia

POST /api/users           # Serwer generuje ID
{
  "name": "Jan"
}
// Response: 201 Created, Location: /api/users/123

PUT /api/users/123        # Klient określa ID
{
  "name": "Jan"
}
// Response: 201 Created lub 200 OK

Reguła: POST gdy serwer generuje identyfikator, PUT gdy klient go zna.

Projektowanie URL i Zasobów

Dobre URL są intuicyjne, przewidywalne i samo-dokumentujące się.

Best Practices dla URL

1. Używaj rzeczowników, nie czasowników:

DOBRZE: GET /api/users
ŹLE:    GET /api/getUsers

DOBRZE: POST /api/users
ŹLE:    POST /api/createUser

2. Liczba mnoga dla kolekcji:

GET /api/users          # Kolekcja
GET /api/users/123      # Pojedynczy zasób

Nie: /api/user, /api/user/123

3. Hierarchia dla zagnieżdżonych zasobów:

GET /api/users/123/orders           # Zamówienia użytkownika 123
GET /api/users/123/orders/456       # Zamówienie 456 użytkownika 123
POST /api/users/123/orders          # Nowe zamówienie dla użytkownika 123

4. Maksymalnie 2-3 poziomy zagnieżdżenia:

DOBRZE: /api/users/123/orders
ŹLE:    /api/users/123/orders/456/items/789/details

5. Kebab-case dla wieloczłonowych nazw:

DOBRZE: /api/order-items
ŹLE:    /api/orderItems, /api/order_items

Query Parameters vs Path Parameters

Path parameters - identyfikują konkretny zasób:

GET /api/users/123              # User o ID 123
GET /api/products/laptop-dell   # Produkt po slug

Query parameters - filtrowanie, sortowanie, paginacja:

GET /api/users?status=active
GET /api/users?role=admin&sort=-createdAt
GET /api/users?page=2&limit=20
GET /api/products?category=electronics&min_price=100

Akcje Niestandardowe

Czasem REST nie wystarczy. Jak obsłużyć akcje jak "aktywuj użytkownika" czy "wyślij email"?

Opcja 1: PATCH z akcją

PATCH /api/users/123
{ "status": "active" }

Opcja 2: Zagnieżdżony zasób akcji

POST /api/users/123/activation
POST /api/orders/456/cancellation

Opcja 3: Dedykowany endpoint (RPC-style)

POST /api/users/123/activate
POST /api/emails/send

Opcje 1 i 2 są bardziej RESTful, opcja 3 jest bardziej intuicyjna dla developerów.

Kody Statusu HTTP

Prawidłowe kody statusu komunikują wynik operacji bez potrzeby parsowania body.

Najważniejsze Kody

2xx - Sukces:

200 OK              - Sukces GET, PUT, PATCH
201 Created         - Sukces POST (zasób utworzony)
202 Accepted        - Request przyjęty do przetworzenia (async)
204 No Content      - Sukces DELETE, PUT bez body

3xx - Przekierowania:

301 Moved Permanently   - Zasób przeniesiony na stałe
304 Not Modified        - Cache jest aktualny

4xx - Błędy Klienta:

400 Bad Request         - Nieprawidłowa składnia requestu
401 Unauthorized        - Brak uwierzytelnienia
403 Forbidden           - Brak autoryzacji
404 Not Found           - Zasób nie istnieje
405 Method Not Allowed  - Metoda niedozwolona dla zasobu
409 Conflict            - Konflikt (np. duplikat)
422 Unprocessable Entity - Walidacja nie przeszła
429 Too Many Requests   - Rate limit przekroczony

5xx - Błędy Serwera:

500 Internal Server Error - Nieoczekiwany błąd
502 Bad Gateway           - Błąd proxy/gateway
503 Service Unavailable   - Serwis chwilowo niedostępny
504 Gateway Timeout       - Timeout na proxy

401 vs 403 - Częste Pytanie

// 401 Unauthorized - brak tokena lub token nieprawidłowy
// "Nie wiem kim jesteś, zaloguj się"
if (!token) {
  return res.status(401).json({ error: 'Authentication required' });
}

// 403 Forbidden - token ok, ale brak uprawnień
// "Wiem kim jesteś, ale nie masz dostępu"
if (user.role !== 'admin') {
  return res.status(403).json({ error: 'Admin access required' });
}

400 vs 422 - Walidacja

// 400 Bad Request - nieprawidłowy JSON, brak wymaganych pól
// Problem ze składnią requestu
{
  "error": "Invalid JSON syntax"
}

// 422 Unprocessable Entity - JSON poprawny, ale dane nie przeszły walidacji
// Request zrozumiany, ale semantycznie niepoprawny
{
  "error": "Validation failed",
  "details": [
    { "field": "email", "message": "Invalid email format" },
    { "field": "password", "message": "Must be at least 8 characters" }
  ]
}

Response dla Błędów

Ustandaryzuj format błędów w całym API:

// Error response format
{
  "error": {
    "code": "VALIDATION_ERROR",       // Kod do programistycznego użycia
    "message": "Validation failed",   // Czytelny opis
    "details": [                      // Szczegóły (opcjonalne)
      { "field": "email", "message": "Invalid format" }
    ],
    "timestamp": "2026-01-08T10:30:00Z",
    "requestId": "abc-123-def"        // Do debugowania
  }
}

Paginacja

Paginacja jest niezbędna dla endpointów zwracających kolekcje. Bez niej duże zbiory danych zabiją wydajność.

Offset Pagination

Najprostsza, ale ma problemy:

GET /api/users?page=2&limit=20
GET /api/users?offset=20&limit=20
// Response
{
  "data": [...],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 1500,
    "totalPages": 75
  }
}

Problemy offset pagination:

  1. Wydajność - OFFSET 100000 jest wolny (DB musi przeskanować i pominąć wiersze)
  2. Niestabilność - dodanie/usunięcie elementu przesuwa wszystkie strony
  3. Duplikaty/braki - przy zmianach danych podczas paginacji

Cursor Pagination

Używa nieprzejrzystego kursora zamiast numeru strony:

GET /api/users?cursor=eyJpZCI6MTAwfQ&limit=20
// Response
{
  "data": [...],
  "pagination": {
    "nextCursor": "eyJpZCI6MTIwfQ",
    "prevCursor": "eyJpZCI6ODV9",
    "hasMore": true
  }
}

Implementacja:

// Cursor to zakodowany identyfikator ostatniego elementu
const cursor = Buffer.from(JSON.stringify({ id: lastItem.id }))
  .toString('base64');

// Dekodowanie i query
const { id } = JSON.parse(Buffer.from(cursor, 'base64').toString());
const items = await db.query(
  'SELECT * FROM users WHERE id > $1 ORDER BY id LIMIT $2',
  [id, limit + 1]
);

Zalety cursor:

  • Wydajny (używa indeksów)
  • Stabilny przy zmianach danych
  • Działa dla nieskończonego scrollowania

Wady:

  • Brak "skoku" do strony N
  • Bardziej skomplikowana implementacja

Keyset Pagination

Podobna do cursor, ale używa jawnych wartości:

GET /api/users?after_id=100&limit=20
GET /api/posts?after_created_at=2026-01-08T10:00:00Z&limit=20

Wymaga unikalnego, sortowalnego pola (ID, timestamp).

Która Strategia Kiedy?

Strategia Kiedy używać
Offset Małe zbiory (<10k), potrzeba skoku do strony
Cursor Duże zbiory, real-time dane, infinite scroll
Keyset Proste przypadki cursor, publiczne API

Autentykacja i Autoryzacja

Bezpieczeństwo API to podstawa. Musisz znać różne metody autentykacji.

JWT (JSON Web Token)

Najpopularniejsza metoda dla REST API:

const jwt = require('jsonwebtoken');

// Generowanie tokena
const generateToken = (user) => {
  return jwt.sign(
    {
      sub: user.id,
      email: user.email,
      role: user.role
    },
    process.env.JWT_SECRET,
    { expiresIn: '1h' }
  );
};

// Weryfikacja
const authMiddleware = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

Struktura JWT:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.signature
|______ Header ______|_____ Payload _____|_ Signature _|

Access Token + Refresh Token

// Login - zwraca oba tokeny
const accessToken = generateToken(user, '15m');
const refreshToken = generateToken(user, '7d');

// Access token w Authorization header
Authorization: Bearer <access_token>

// Refresh token w httpOnly cookie lub osobnym endpoincie
POST /api/auth/refresh
Cookie: refreshToken=<refresh_token>

// Response
{
  "accessToken": "<new_access_token>"
}

Dlaczego dwa tokeny?

  • Access token: krótki (15min), w pamięci, ryzyko wycieku ograniczone
  • Refresh token: długi (dni), httpOnly cookie, do odświeżania access

OAuth 2.0

Dla integracji z zewnętrznymi serwisami (Google, Facebook):

1. Redirect do authorization server:
   GET https://auth.google.com/authorize?
     client_id=xxx&
     redirect_uri=https://myapp.com/callback&
     scope=email profile&
     response_type=code

2. User loguje się i akceptuje

3. Redirect z code:
   GET https://myapp.com/callback?code=abc123

4. Wymiana code na token (server-to-server):
   POST https://auth.google.com/token
   { code, client_id, client_secret }

5. Response:
   { access_token, refresh_token, expires_in }

API Keys

Prostsze niż JWT, dobre dla server-to-server:

GET /api/data
X-API-Key: sk_live_abcd1234

// lub w query param (mniej bezpieczne)
GET /api/data?api_key=sk_live_abcd1234

Dobre praktyki API keys:

  • Prefix określający typ (sk_ = secret, pk_ = public)
  • Możliwość rotacji bez przerwy
  • Ograniczenie per IP/domena
  • Nie logować w plain text

Bezpieczeństwo API

Rate Limiting

Ochrona przed nadużyciami:

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 60 * 1000,      // 1 minuta
  max: 100,                  // 100 requestów per window
  message: {
    error: 'Too many requests',
    retryAfter: 60
  },
  standardHeaders: true,     // RateLimit-* headers
  legacyHeaders: false
});

app.use('/api/', limiter);

Response headers:

RateLimit-Limit: 100
RateLimit-Remaining: 45
RateLimit-Reset: 1704715800
Retry-After: 60

CORS (Cross-Origin Resource Sharing)

const cors = require('cors');

app.use(cors({
  origin: ['https://myapp.com', 'https://admin.myapp.com'],
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,           // Pozwól na cookies
  maxAge: 86400                // Preflight cache: 24h
}));

Preflight request:

OPTIONS /api/users
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

Walidacja i Sanityzacja

const Joi = require('joi');
const sanitizeHtml = require('sanitize-html');

// Schema walidacji
const createUserSchema = Joi.object({
  name: Joi.string().min(2).max(100).required(),
  email: Joi.string().email().required(),
  password: Joi.string().min(8).pattern(/[A-Z]/).pattern(/[0-9]/).required()
});

// Middleware walidacji
const validate = (schema) => (req, res, next) => {
  const { error } = schema.validate(req.body, { abortEarly: false });
  if (error) {
    return res.status(422).json({
      error: 'Validation failed',
      details: error.details.map(d => ({
        field: d.path.join('.'),
        message: d.message
      }))
    });
  }
  next();
};

// Sanityzacja HTML (ochrona przed XSS)
const sanitizeInput = (input) => {
  return sanitizeHtml(input, {
    allowedTags: [],
    allowedAttributes: {}
  });
};

OWASP Top 10 dla API

  1. Broken Object Level Authorization - sprawdzaj czy user ma dostęp do zasobu
  2. Broken Authentication - silne hasła, rate limiting na login
  3. Excessive Data Exposure - zwracaj tylko potrzebne pola
  4. Lack of Resources & Rate Limiting - limity requestów
  5. Broken Function Level Authorization - sprawdzaj role na każdym endpoint
  6. Mass Assignment - whitelist dozwolonych pól
  7. Security Misconfiguration - wyłącz debug w produkcji
  8. Injection - parametryzowane zapytania
  9. Improper Assets Management - dokumentuj wszystkie endpointy
  10. Insufficient Logging & Monitoring - loguj i monitoruj

Wydajność i Caching

HTTP Caching

// Cache-Control header
app.get('/api/products', (req, res) => {
  res.set({
    'Cache-Control': 'public, max-age=3600',  // Cache 1h
    'ETag': '"abc123"'                         // Dla conditional requests
  });
  res.json(products);
});

// Dla danych użytkownika
app.get('/api/users/me', (req, res) => {
  res.set({
    'Cache-Control': 'private, no-cache',     // Nie cache'uj publicznie
    'ETag': `"user-${user.id}-${user.updatedAt}"`
  });
  res.json(user);
});

ETag i Conditional Requests

// Request z If-None-Match
// GET /api/products
// If-None-Match: "abc123"

app.get('/api/products', (req, res) => {
  const etag = generateETag(products);

  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end();  // Not Modified
  }

  res.set('ETag', etag);
  res.json(products);
});

Kompresja

const compression = require('compression');

app.use(compression({
  filter: (req, res) => {
    if (req.headers['x-no-compression']) return false;
    return compression.filter(req, res);
  },
  threshold: 1024,  // Nie kompresuj < 1KB
  level: 6          // Balans między szybkością a rozmiarem
}));

Partial Response (Fields Selection)

Pozwól klientom żądać tylko potrzebnych pól:

GET /api/users?fields=id,name,email
app.get('/api/users', (req, res) => {
  const fields = req.query.fields?.split(',');

  let query = User.find();
  if (fields) {
    query = query.select(fields.join(' '));
  }

  const users = await query;
  res.json(users);
});

Dokumentacja - OpenAPI/Swagger

OpenAPI Specification

openapi: 3.0.3
info:
  title: My REST API
  version: 1.0.0
  description: API for managing users

servers:
  - url: https://api.myapp.com/v1

paths:
  /users:
    get:
      summary: Get all users
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [active, inactive]
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        '200':
          description: List of users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'

    post:
      summary: Create user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUser'
      responses:
        '201':
          description: User created
        '422':
          description: Validation error

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email
        createdAt:
          type: string
          format: date-time

    CreateUser:
      type: object
      required:
        - name
        - email
        - password
      properties:
        name:
          type: string
          minLength: 2
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8

Best Practices Dokumentacji

  1. Dokumentuj wszystkie endpointy - nie zostawiaj "ukrytych" API
  2. Przykłady request/response - konkretne, nie abstrakcyjne
  3. Kody błędów - wszystkie możliwe błędy z opisami
  4. Wersjonowanie docs - osobna dokumentacja per wersja API
  5. Interaktywne testowanie - Swagger UI, Postman collections
  6. Changelog - co się zmieniło między wersjami

Testowanie REST API

Unit Tests

describe('UsersController', () => {
  describe('POST /users', () => {
    it('should create user with valid data', async () => {
      const userData = { name: 'Jan', email: 'jan@example.com', password: 'Secret123' };

      const result = await usersController.create(userData);

      expect(result.name).toBe('Jan');
      expect(result.email).toBe('jan@example.com');
      expect(result.password).toBeUndefined(); // Nie zwracaj hasła
    });

    it('should reject duplicate email', async () => {
      usersRepository.findByEmail.mockResolvedValue({ id: 1 });

      await expect(usersController.create(userData))
        .rejects.toThrow(ConflictException);
    });
  });
});

Integration Tests

const request = require('supertest');

describe('Users API', () => {
  it('POST /api/users - creates user', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ name: 'Jan', email: 'jan@example.com', password: 'Secret123' })
      .expect(201);

    expect(response.body.id).toBeDefined();
    expect(response.body.email).toBe('jan@example.com');
  });

  it('POST /api/users - rejects invalid email', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ name: 'Jan', email: 'invalid', password: 'Secret123' })
      .expect(422);

    expect(response.body.error).toBe('Validation failed');
  });

  it('GET /api/users - requires authentication', async () => {
    await request(app)
      .get('/api/users')
      .expect(401);
  });

  it('GET /api/users - returns users with valid token', async () => {
    const response = await request(app)
      .get('/api/users')
      .set('Authorization', `Bearer ${validToken}`)
      .expect(200);

    expect(Array.isArray(response.body)).toBe(true);
  });
});

Contract Testing

Weryfikacja że API spełnia kontrakt (OpenAPI spec):

const SwaggerParser = require('@apidevtools/swagger-parser');
const { OpenApiValidator } = require('express-openapi-validate');

const spec = await SwaggerParser.bundle('./openapi.yaml');
const validator = new OpenApiValidator(spec);

it('response matches OpenAPI spec', async () => {
  const response = await request(app)
    .get('/api/users')
    .set('Authorization', `Bearer ${token}`);

  const valid = validator.validateResponse('get', '/users', response);
  expect(valid.errors).toHaveLength(0);
});

Podsumowanie: REST API Checklist

Projektowanie URL

  • Rzeczowniki, nie czasowniki
  • Liczba mnoga dla kolekcji
  • Konsekwentne nazewnictwo (kebab-case)
  • Max 2-3 poziomy zagnieżdżenia

Metody HTTP

  • Właściwe metody dla operacji
  • PUT dla pełnej aktualizacji, PATCH dla częściowej
  • Idempotentność gdzie potrzebna

Response

  • Prawidłowe kody statusu
  • Spójny format błędów
  • Paginacja dla list
  • Tylko potrzebne dane

Bezpieczeństwo

  • HTTPS
  • JWT/OAuth dla autentykacji
  • Rate limiting
  • Walidacja inputu
  • CORS skonfigurowany

Wydajność

  • Cache headers
  • Kompresja
  • Fields selection
  • Efektywna paginacja

Dokumentacja

  • OpenAPI/Swagger
  • Przykłady
  • Kody błędów
  • Changelog

Zobacz też


Chcesz Więcej Pytań o REST API?

Mamy 50 pytań rekrutacyjnych o REST API best practices - od podstaw przez bezpieczeństwo po zaawansowane wzorce. Każde z pełną odpowiedzią w formacie "30 sekund / 2 minuty".

Zdobądź Pełny Dostęp do Wszystkich Pytań


Napisane przez zespół Flipcards, na podstawie doświadczeń z projektowania i review API w projektach enterprise.

Chcesz więcej pytań rekrutacyjnych?

To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.

Kup pełny dostęp Zobacz bezpłatny podgląd
Powrót do blogu

Zostaw komentarz

Pamiętaj, że komentarze muszą zostać zatwierdzone przed ich opublikowaniem.