Nested Relations

Nuxt Auto API supports advanced nested relation loading with field selection, filtering, and pagination at each level of nesting.

Nested Relations

Nuxt Auto API supports advanced nested relation loading with field selection, filtering, and pagination at each level of nesting.

Basic Nested Relations

The simplest form uses dot notation to load nested relations:

// GET /api/users?include=posts.comments.author

{
  "data": [
    {
      "id": 1,
      "name": "John Doe",
      "posts": [
        {
          "id": 1,
          "title": "Hello World",
          "comments": [
            {
              "id": 1,
              "body": "Great post!",
              "author": {
                "id": 2,
                "name": "Jane Smith"
              }
            }
          ]
        }
      ]
    }
  ]
}

Field Selection

Select specific fields from relations using bracket syntax:

// GET /api/users?include=posts[id,title,createdAt]

{
  "data": [
    {
      "id": 1,
      "name": "John Doe",
      "posts": [
        {
          "id": 1,
          "title": "Hello World",
          "createdAt": "2024-01-01T00:00:00Z"
          // Only selected fields included
        }
      ]
    }
  ]
}

Filtering Relations

Filter related records using curly brace syntax:

// GET /api/users?include=posts{filter:{published:true}}

// Only published posts will be included
{
  "data": [
    {
      "id": 1,
      "name": "John Doe",
      "posts": [
        // Only posts where published = true
      ]
    }
  ]
}

Advanced Filtering

Use the same filter operators as the main list endpoint:

// GET /api/users?include=posts{filter:{createdAt:{$gte:"2024-01-01"}}}
// GET /api/users?include=posts{filter:{title:{$contains:"nuxt"}}}

Pagination on Relations

Limit and offset related records:

// GET /api/users?include=posts{limit:5}

{
  "data": [
    {
      "id": 1,
      "name": "John Doe",
      "posts": [
        // Only first 5 posts
      ]
    }
  ]
}

With offset:

// GET /api/users?include=posts{limit:5,offset:10}

Combining Features

Combine field selection, filtering, and pagination:

// GET /api/users?include=posts[id,title]{limit:10,filter:{published:true}}

{
  "data": [
    {
      "id": 1,
      "name": "John Doe",
      "posts": [
        {
          "id": 1,
          "title": "Hello World"
          // Only id and title, only published, max 10
        }
      ]
    }
  ]
}

Deep Nesting

Combine all features with deep nesting:

// GET /api/users?include=posts[id,title]{limit:5,filter:{published:true}}.comments[id,body]{limit:3}.author[id,name]

{
  "data": [
    {
      "id": 1,
      "name": "John Doe",
      "posts": [
        {
          "id": 1,
          "title": "Hello World",
          "comments": [
            {
              "id": 1,
              "body": "Great post!",
              "author": {
                "id": 2,
                "name": "Jane Smith"
              }
            }
          ]
        }
      ]
    }
  ]
}

Multiple Relations

Include multiple relations at the same level:

// GET /api/users?include=posts[id,title],comments[id,body],profile[bio,avatar]

{
  "data": [
    {
      "id": 1,
      "name": "John Doe",
      "posts": [...],
      "comments": [...],
      "profile": {
        "bio": "...",
        "avatar": "..."
      }
    }
  ]
}

Configuration

Configure nested relations behavior in nuxt.config.ts:

export default defineNuxtConfig({
  autoApi: {
    relations: {
      // Maximum nesting depth (default: 3)
      maxDepth: 5,

      // Allow field selection (default: true)
      allowFieldSelection: true,

      // Allow filtering on relations (default: true)
      allowFiltering: true,

      // Allow pagination on relations (default: true)
      allowPagination: true,
    }
  }
})

Max Depth Protection

To prevent performance issues and circular dependencies, nested relations are limited to a maximum depth (default: 3 levels).

Exceeding the max depth will log a warning and stop further nesting:

// If maxDepth is 3, this will only nest 3 levels deep
// GET /api/a?include=b.c.d.e.f
// Will load: a -> b -> c -> d (stops here)

Authorization on Nested Relations

Authorization checks apply at each level:

  • Collection-level: User must have read permission for each related resource
  • Object-level: Object-level authorization functions execute for each related record
  • Field-level: Field permissions apply to nested relation fields

Example:

// If user doesn't have permission to read 'posts', this will fail:
// GET /api/users?include=posts

// If user can't read certain fields in posts, they'll be filtered out:
// GET /api/users?include=posts[id,title,secret]
// 'secret' field will be omitted if user doesn't have permission

Performance Considerations

Database Queries

Each level of nesting may result in additional database queries. The exact number depends on your database client and Drizzle's query optimization.

Indexes

For optimal performance with filtering on relations:

-- Index the fields you filter by
CREATE INDEX idx_posts_published ON posts(published);
CREATE INDEX idx_posts_created_at ON posts(created_at);

Field Selection Benefits

Selecting only needed fields reduces:

  • Network bandwidth
  • JSON serialization time
  • Client memory usage
// Good: Only fetch what you need
include=posts[id,title]

// Less efficient: Fetch everything
include=posts

Pagination Benefits

Limit relation results to avoid loading huge datasets:

// Good: Limit related records
include=posts{limit:10}

// Potentially slow: Load all posts for each user
include=posts

Common Patterns

Blog Posts with Recent Comments

GET /api/posts?include=comments[id,body,createdAt]{limit:5,filter:{createdAt:{$gte:"2024-01-01"}}}.author[name,avatar]

Users with Recent Activity

GET /api/users?include=posts[id,title]{limit:3},comments[id,body]{limit:5}

Nested Categories

GET /api/categories?include=subcategories.products[id,name,price]{limit:10}

Error Handling

Invalid include syntax returns a 400 error:

// Invalid: Unknown relation
GET /api/users?include=nonexistent
// Response: 400 Bad Request - "Unknown relation: nonexistent"

// Invalid: Exceeds max depth
GET /api/users?include=a.b.c.d.e.f.g
// Response: Warning logged, nesting stops at max depth

TypeScript Support

The frontend composables infer nested types:

const { data } = await useAutoApiFetch('users', {
  query: {
    include: 'posts.comments.author'
  }
})

// TypeScript knows data includes nested relations
data.value[0].posts[0].comments[0].author.name

Comparison with GraphQL

While not as flexible as GraphQL, nested relations provide similar capabilities:

FeatureNested RelationsGraphQL
Field selection
Nested loading
Filtering
Pagination
Type safety✅ (TypeScript)
No schema required
Learning curveLowMedium

Best Practices

  1. Always select fields: Don't load more data than needed
  2. Use pagination: Limit related records to prevent performance issues
  3. Filter early: Apply filters at the relation level when possible
  4. Monitor depth: Keep nesting to 2-3 levels for best performance
  5. Index filter fields: Create database indexes for filtered fields
  6. Cache responses: Use HTTP caching for frequently accessed nested data

Migration from Simple Includes

Existing code with simple includes continues to work:

// Old syntax (still works)
GET /api/users?include=posts,comments

// New enhanced syntax (more powerful)
GET /api/users?include=posts[id,title]{limit:10},comments[id,body]{limit:5}

The system automatically detects and handles both syntaxes.

Need a Landing Page?

Modern landing pages with optional modules (blog, docs, forms, i18n). Let's discuss your project.

Build Your MVP

Full-stack SaaS development. Expert in database design, multi-tenancy, and scalable architecture.

Deployment Help

Dockerize your backend, set up CI/CD pipelines, deploy to Cloudflare or Hetzner. Early-stage setup.

Suggest a SaaS Tool

Missing a calculator or tool? Suggest what you'd like to see on our site.