Integrating Vapor

Raptor can run directly on Vapor, enabling server-side rendering, dynamic data, sessions, and interactive server actions. First, import RaptorVapor:

import RaptorVapor

This unlocks all Vapor-specific integrations, including routing, property wrappers, and server actions.

Mounting a Site

Raptor handles all page routing automatically when mounted into a Vapor app. Once mounted, pages and posts are rendered server-side, and URLs are resolved automatically.

@main
struct Server {
    static func main() async throws {
        let app = try await Application.make(.detect)

        app.sessions.use(.database)
        app.middleware.use(app.sessions.middleware)

        try await app.mount(MySite())

        try await app.execute()
        try await app.asyncShutdown()
    }
}

Accessing Server Data in Views

Raptor provides several property wrappers that expose server-side state directly inside your views.

All wrappers follow the same pattern: 1. Define a key 2. Register or set values in Vapor 3. Read them declaratively in your page

Defining Storage Keys

Each storage mechanism uses a strongly typed key with a default value.

struct VisitCountKey: SessionStorageKey {
   static let defaultValue = 0
}

struct BannerMessageKey: AppStorageKey {
   static let defaultValue = ""
}

struct RequestIDKey: RequestedValueKey {
   static let defaultValue = "unknown"
}

App-Wide Storage

Use @AppStorage for global, application-level state that is shared across all users and requests. This is useful for site-wide banners, feature toggles, announcements, or CMS-driven content.

@AppStorage(BannerMessageKey.self)
private var bannerMessage

Updating App-Wide Values

App storage values are typically updated from administrative routes, background jobs, or CMS integrations.

app.post("admin", "banner") { request in
    request.appStorage.set(
        BannerMessageKey.self,
        to: "Scheduled maintenance tonight at 10 PM."
    )

    return "Banner message updated"
}

Once set, app storage values are available to all rendered pages automatically.

struct MainLayout: Layout {
   @AppStorage(BannerMessageKey.self)
   private var bannerMessage

   var body: some Document {
      Banner {
         if bannerMessage.isEmpty == false {
            Text(bannerMessage)
               .font(.callout)
         }
      }
   }
}

Because app storage is global, changes take effect immediately for all users without requiring redeploys or rebuilds.

Session Storage

Use @SessionStorage to persist values across multiple requests for the same user. Session storage is ideal for tracking visit counts, onboarding state, feature flags, or other user-specific data that should survive page reloads.

@SessionStorage(VisitCountKey.self)
private var visitCount

Updating Session Values

Session values are typically updated inside route handlers or middleware, before rendering a page.

app.get("increment") { request in
   let current = request.sessionStorage.get(VisitCountKey.self)

   request.sessionStorage.set(
      VisitCountKey.self,
      to: current + 1
   )

   return request.redirect(to: "/")
}

Once stored, session values are automatically available to pages rendered for that session.

struct Home: Page {
   @SessionStorage(VisitCountKey.self)
   private var visitCount

   var body: some HTML {
      Section {
         Text("Welcome back!")
         Text("Visits this session: \(visitCount)")
      }
   }
}

This allows you to keep request handling and view rendering cleanly separated, while still sharing state safely across user interactions.

Request-Scoped Values

Use @RequestedValue for data that should exist only for the lifetime of a single HTTP request. These values are ideal for request IDs, authentication context, tracing metadata, or other per-request information.

@RequestedValue(RequestIDKey.self)
private var requestID

Setting Request Values

Request-scoped values are typically set in middleware, ensuring they are available to all pages rendered during that request.

app.middleware.use { request, next in
   request.requestedValues.set(
      RequestIDKey.self,
      to: request.id
   )
   return next.respond(to: request)
}

Once set, the value can be accessed from any page rendered for that request using @RequestedValue.

struct Home: Page {
   @RequestedValue(RequestIDKey.self)
   private var requestID

   var body: some HTML {
      Text("Request ID: \(requestID)")
   }
}

This approach keeps routing declarative, avoids page-specific endpoints, and ensures request metadata flows cleanly into your views without manual wiring.

Rendering Pages from Requests

When running on Vapor, pages are rendered by calling render(_:) on the current request. This executes the full server-side rendering pipeline and returns a complete HTML response.

app.get { request in
   try request.render(Home())
}

Calling render(_:) automatically resolves the site’s layout, infers the active locale from the request URL, and produces a fully rendered HTML Response.

You can also render individual posts directly:

app.get("preview") { request in
   @Environment(\.posts) var posts

   guard let post = posts.first else {
      throw Abort(.notFound)
   }

   return try request.render(post)
}

The correct post page and layout are resolved automatically based on your site configuration.

Server Actions

Server actions allow client-side interactions (such as button presses) to trigger server-side logic without writing JavaScript.

Defining a Server Action

struct IncrementCounter: ServerAction {
   static let endpoint = "increment-counter"

   func handle(_ request: Request) throws -> ServerActionResult {
      let count = request.sessionStorage.get(VisitCountKey.self)
      request.sessionStorage.set(
         VisitCountKey.self,
         to: count + 1
      )
      return .reload
   }
}

Each action: - Decodes from JSON - Executes server-side - Returns a declarative result (reload, redirect, update, etc.)

Triggering Actions from Views

Server actions can be attached directly to buttons.

Button("Increment", action: IncrementCounter())

When executed, the button performs a secure POST request to the action’s endpoint.