Skip to content

Modern Star Wars API with aggregation, expand, filtering and pagination.

License

Notifications You must be signed in to change notification settings

maiano/sw-next-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

29 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Star Wars API - BFF Edition

A modern, frontend-friendly Star Wars API built with Next.js 16 and TypeScript.

🌐 Live Demo: https://sw-next-api.vercel.app

πŸ“– Русская вСрсия: README.ru.md

Base URL

All API endpoints are relative. Prepend your base URL:

  • Local development: http://localhost:3000
  • Production (example): https://sw-next-api.vercel.app

Example: GET /api/v1/people/1 becomes GET http://localhost:3000/api/v1/people/1

Why This API Exists

This API exists because most Star Wars APIs are data-oriented, not UI-oriented.

Traditional APIs like SWAPI require multiple requests to display a single UI screen. This project demonstrates how the same data can be exposed in a frontend-friendly way using the Backend for Frontend (BFF) pattern.

The Problem with Traditional APIs

To display a character with their homeworld and films:

SWAPI approach:

GET /people/1           # Luke Skywalker
GET /planets/1          # Tatooine
GET /films/1            # A New Hope
GET /films/2            # Empire Strikes Back
GET /films/3            # Return of the Jedi
# ... 5+ requests for one character

This API:

GET /api/v1/people/1?expand=homeworld,films
# 1 request, all data included

Features

πŸš€ Controlled Expansion

Query exactly what you need:

# Just basic data (minimal projection)
GET /api/v1/people/1

# Include homeworld
GET /api/v1/people/1?expand=homeworld

# Multiple levels (depth limited to 2)
GET /api/v1/people/1?expand=films.characters

πŸ“„ Smart Pagination

GET /api/v1/people?page=2&limit=10

Response:
{
  "page": 2,
  "limit": 10,
  "total": 82,
  "pages": 9,
  "results": [...]
}

πŸ” Search & Filter

# Search by name
GET /api/v1/people?search=luke

# Filter by attributes
GET /api/v1/people?gender=female

# Sort results
GET /api/v1/people?sort=-name  # descending
GET /api/v1/films?sort=episode  # ascending

🎯 Combine Everything

GET /api/v1/people?search=skywalker&gender=male&sort=name&page=1&limit=5&expand=homeworld

⚑ Extended Metadata

Unlike SWAPI, this API includes additional metadata:

People:

  • isForceUser - Force-sensitive characters
  • isJedi / isSith - Force alignment
  • faction - rebels, empire, republic, separatists, civilian

Starships:

  • is_military - Military vs civilian classification
  • faction - Which faction operates the vessel
# Find all Jedi
GET /api/v1/people?isJedi=true

# Find Imperial military ships
GET /api/v1/starships?faction=empire&is_military=true

API Endpoints

Single Resources

  • GET /api/v1/people/{id}
  • GET /api/v1/films/{id}
  • GET /api/v1/planets/{id}
  • GET /api/v1/species/{id}
  • GET /api/v1/starships/{id}
  • GET /api/v1/vehicles/{id}

Lists (with pagination, search, filtering, sorting)

  • GET /api/v1/people
  • GET /api/v1/films
  • GET /api/v1/planets
  • GET /api/v1/species
  • GET /api/v1/starships
  • GET /api/v1/vehicles

Data Dependency Graph

Film is the central entity connecting all other entity types. Here's the complete relationship structure:

                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                              β”‚              FILM                   β”‚
                              β”‚  (Central Hub - All relationships)  β”‚
                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β”‚     β”‚     β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚     β”‚
                    β”‚                          β”‚     β”‚
                    β–Ό                          β–Ό     β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚ PERSON   │◄───────────── PLANET   β”‚ β”‚  SPECIES    β”‚
              β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚                         β”‚           β”‚
                   β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”˜     β”Œβ”€β”€β”€β”€β”€β”˜
                   β”‚   β”‚                 β”‚         β”‚
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”
       β”‚           β”‚   β”‚             β”‚   β”‚   β”‚             β”‚
       β–Ό           β–Ό   β–Ό             β–Ό   β–Ό   β–Ό             β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ STARSHIP β”‚ β”‚ VEHICLE  β”‚ β”‚  (Homeworld) β”‚      β”‚  (Pilots)   β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Relationship Matrix

From β†’ To Type Bidirectional Depth Limit
Film β†’ Person many-to-many βœ“ 2
Film β†’ Planet many-to-many βœ“ 2
Film β†’ Species many-to-many βœ“ 2
Film β†’ Starship many-to-many βœ“ 2
Film β†’ Vehicle many-to-many βœ“ 2
Person β†’ Planet (homeworld) many-to-one βœ“ 2
Person β†’ Species many-to-many βœ“ 2
Person β†’ Starship many-to-many βœ— 2
Person β†’ Vehicle many-to-many βœ“ 2
Species β†’ Planet (homeworld) many-to-one βœ— 2

Key Points:

  • Maximum expansion depth: 2 levels (prevents infinite loops)
  • Normalized responses: Related entities return as minimal objects ({entityId, id, name})
  • Type-safe: All relationships use EntityId for type safety
  • Efficient: Prevents over-fetching with controlled expansion

Example Expansion

// GET /api/v1/films/1?expand=characters.homeworld

{
  "entityId": 1,
  "title": "A New Hope",
  "characters": [           // Depth 1
    {
      "entityId": 1,
      "name": "Luke Skywalker",
      "homeworld": {        // Depth 2 (stops here)
        "entityId": 1,
        "name": "Tatooine"
      }
    }
  ]
}

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   API Routes    β”‚  Next.js API handlers
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   BFF Layer     β”‚  Aggregates related data
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Projections    β”‚  Transforms to response models
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Expand System   β”‚  Controlled depth expansion
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Resolver      β”‚  Universal entity lookup
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Normalized     β”‚  In-memory data store
β”‚     Data        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Patterns

BFF (Backend for Frontend):

  • Aggregates multiple entities in one request
  • Returns UI-ready data structures
  • Eliminates waterfall requests

Controlled Expansion:

  • Whitelist of expandable fields per entity
  • Depth limit prevents graph explosion
  • Type-safe expansion tree

Universal Resolver:

  • Single lookup function for all entities
  • Supports both EntityId and slug
  • Centralized mapping registry

Response Projections:

  • Fixed contracts for API responses
  • Minimal projections for related entities
  • Prevents over-fetching

Normalized Data:

  • camelCase naming (heightCm, birthYearBBY)
  • Proper types (numbers instead of strings)
  • Minimal objects for relationships

Getting Started

# Install dependencies
npm install

# Run development server
npm run dev

# Build for production
npm run build

# Run production server
npm start

Example Usage

Fetch a character with related data

const response = await fetch(
  'http://localhost:3000/api/v1/people/1?expand=homeworld,films'
);
const luke = await response.json();

console.log(luke.name);           // "Luke Skywalker"
console.log(luke.homeworld.name); // "Tatooine"
console.log(luke.films.length);   // 4

Search and paginate

const response = await fetch(
  'http://localhost:3000/api/v1/people?search=skywalker&page=1&limit=5'
);
const data = await response.json();

console.log(data.total);          // 3
console.log(data.results.length); // 3
console.log(data.results[0].name); // "Anakin Skywalker"

Complex query with filters

const response = await fetch(
  'http://localhost:3000/api/v1/people?isJedi=true&faction=republic&expand=homeworld&limit=10'
);
const jedi = await response.json();

// All Jedi from the Republic with their homeworlds

Technology Stack

  • Next.js 16 - App Router with React Server Components
  • TypeScript - Full type safety across the stack
  • Tailwind CSS 4 - Modern styling with CSS-first approach
  • In-memory data - Fast, no database needed
  • Vercel - Serverless deployment

SEO & Performance

  • βœ… JSON-LD structured data for rich snippets
  • βœ… OpenGraph & Twitter cards
  • βœ… Dynamic OG image generation
  • βœ… Canonical URLs
  • βœ… Sitemap.xml
  • βœ… Robots.txt (API endpoints excluded from indexing)

License

MIT


Repository: github.com/maiano/sw-api

Author: maiano

About

Modern Star Wars API with aggregation, expand, filtering and pagination.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages