Nuxt

Nuxt 3+ integration with Litestar Vite for universal SSR applications.

Quick Start

litestar assets init --template nuxt

This creates a Nuxt 3 project with TypeScript support and SSR capabilities.

Project Structure

Nuxt applications use the Nuxt module for seamless integration:

my-app/
├── app.py              # Litestar backend
├── package.json
├── nuxt.config.ts      # Nuxt configuration with Litestar module
├── tsconfig.json
├── app.vue             # Root component
├── pages/
│   └── index.vue       # Pages (file-based routing)
├── composables/
│   └── useApi.ts       # Composables for API calls
└── generated/          # Generated types from OpenAPI

Backend Setup

examples/nuxt/app.py
"""Nuxt example - shared "Library" backend for Nuxt SSR frontend.

Nuxt runs as an SSR server. In dev mode, Litestar proxies all non-API
routes to the Nuxt dev server. In production, run Nuxt and Litestar
as separate services behind a reverse proxy.

All examples in this repository expose the same backend:
- `/api/summary` - overview + featured book
- `/api/books` - list of books
- `/api/books/{book_id}` - single book

Dev mode (default):
    litestar --app-dir examples/nuxt run

Production (two services behind reverse proxy):
    # Terminal 1: Nuxt SSR server
    litestar --app-dir examples/nuxt assets build
    litestar --app-dir examples/nuxt assets serve
    # Terminal 2: Litestar API server
    VITE_DEV_MODE=false litestar --app-dir examples/nuxt run --port 8001
"""

import os
from pathlib import Path

from litestar import Controller, Litestar, get
from litestar.exceptions import NotFoundException
from msgspec import Struct

from litestar_vite import PathConfig, RuntimeConfig, TypeGenConfig, ViteConfig, VitePlugin

here = Path(__file__).parent
DEV_MODE = os.getenv("VITE_DEV_MODE", "true").lower() in {"true", "1", "yes"}


class Book(Struct):
    id: int
    title: str
    author: str
    year: int
    tags: list[str]


class Summary(Struct):
    app: str
    headline: str
    total_books: int
    featured: Book


BOOKS: list[Book] = [
    Book(id=1, title="Async Python", author="C. Developer", year=2024, tags=["python", "async"]),
    Book(id=2, title="Type-Safe Web", author="J. Dev", year=2025, tags=["typescript", "api"]),
    Book(id=3, title="Frontend Patterns", author="A. Designer", year=2023, tags=["frontend", "ux"]),
]


def _get_book(book_id: int) -> Book:
    for book in BOOKS:
        if book.id == book_id:
            return book
    raise NotFoundException(detail=f"Book {book_id} not found")


def _get_summary() -> Summary:
    """Build summary data."""
    return Summary(
        app="litestar-vite library", headline="One backend, many frontends", total_books=len(BOOKS), featured=BOOKS[0]
    )


class LibraryController(Controller):
    """Library API controller."""

    @get("/api/summary")
    async def summary(self) -> Summary:
        """Overview endpoint used across all examples."""
        return _get_summary()

    @get("/api/books")
    async def books(self) -> list[Book]:
        """Return all books."""
        return BOOKS

    @get("/api/books/{book_id:int}")
    async def book_detail(self, book_id: int) -> Book:
        """Return a single book by id."""
        return _get_book(book_id)


vite = VitePlugin(
    config=ViteConfig(
        mode="ssr",  # SSR mode: proxy in dev, Node serves HTML in prod
        dev_mode=DEV_MODE,
        paths=PathConfig(root=here),
        types=TypeGenConfig(output=Path("generated"), generate_zod=True),
        # Fixed port for E2E tests - can be removed for local dev or customized for production
        runtime=RuntimeConfig(port=5041),
    )
)

app = Litestar(route_handlers=[LibraryController], plugins=[vite], debug=True)

Key points:

  • mode="framework" enables meta-framework integration mode (aliases: mode="ssr" / mode="ssg")

  • ExternalDevServer delegates dev server to Nuxt

  • TypeGenConfig enables type generation for Nuxt composables

Nuxt Configuration

nuxt.config.ts
import tailwindcss from "@tailwindcss/vite"

// Litestar manages the dev server port via VITE_PORT and runtime config.
// The Nuxt module reads the port automatically - no hardcoding needed.
// LITESTAR_PORT is the backend API server port (default 8000).
const LITESTAR_PORT = process.env.LITESTAR_PORT ?? "8000"

export default defineNuxtConfig({
  compatibilityDate: "2024-11-01",
  devtools: { enabled: true },
  modules: ["litestar-vite-plugin/nuxt"],

  vite: {
    plugins: [tailwindcss()],
  },

  litestar: {
    // API proxy points to the Litestar backend (apiPrefix defaults to "/api")
    apiProxy: `http://127.0.0.1:${LITESTAR_PORT}`,
    verbose: true,
    types: {
      output: "generated",
      openapiPath: "generated/openapi.json",
      routesPath: "generated/routes.json",
    },
  },
})

The Litestar module provides:

  • API proxy configuration (apiProxy)

  • Type generation integration

  • Automatic port coordination with Litestar backend

Runtime Configuration

Nuxt reads runtime configuration from VITE_PORT and LITESTAR_PORT environment variables set by Litestar:

// In composables or pages
const config = useRuntimeConfig()
const apiUrl = config.public.apiProxy  // http://localhost:8000
const apiPrefix = config.public.apiPrefix  // /api

API Integration

Using Composables

// composables/useApi.ts
import type { Summary } from '~/generated/types.gen'
import { route } from '~/generated/routes'

export async function useSummary() {
  const { data, error } = await useFetch<Summary>(
    route('summary'),
    { key: 'summary' }
  )
  return { data, error }
}

Server Routes (SSR)

For server-side API calls during SSR, create a server middleware:

// server/api/[...].ts
export default defineEventHandler((event) => {
  const config = useRuntimeConfig()
  return proxyRequest(event, config.public.apiProxy + event.path)
})

Client-Side Fetch

<script setup lang="ts">
import type { User } from '~/generated/types.gen'
import { route } from '~/generated/routes'

const { data: users } = await useFetch<User[]>(route('users:list'))
</script>

<template>
  <div>
    <h1>Users</h1>
    <ul>
      <li v-for="user in users" :key="user.id">
        {{ user.name }}
      </li>
    </ul>
  </div>
</template>

Running

# Recommended: Litestar manages both servers
litestar run --reload

# Alternative: Run separately
litestar assets serve --production  # Nuxt SSR server
litestar run --reload               # Backend API (in another terminal)

Type Generation

With types=TypeGenConfig() enabled in Python:

litestar assets generate-types

This generates TypeScript types in generated/ (path configured in nuxt.config.ts).

Deployment

For production:

  1. Build Nuxt:

    litestar assets build
    
  2. Serve both apps:

    litestar assets serve --production  # Nuxt SSR server
    litestar run                        # Backend API
    

The --production flag runs npm run serve which starts Nuxt’s production server.

See Also