internal/skills/content/vapor/SKILL.md
Vapor framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Vapor projects, or when the user mentions Vapor. Provides Fluent ORM, async Swift, routing, middleware, and server-side Swift guidelines.
npx skillsauth add ar4mirez/samuel vaporInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Applies to: Vapor 4.x, Swift 5.9+, Fluent ORM, Leaf Templates, Server-Side Swift Language Guide: @.claude/skills/swift-guide/SKILL.md
Vapor is a server-side Swift framework for building web applications, REST APIs, and backend services. It provides type-safe routing, Fluent ORM, async/await concurrency, JWT authentication, and Leaf templating.
Use Vapor when:
Consider alternatives when:
@main entry point pattern with Application.makeRouteCollection controllers@ID, @Field, @Parent, @Children) for modelsContent protocol for all request/response DTOsValidatable protocol for input validation on every endpointAsyncMiddleware for cross-cutting concerns (auth, logging, CORS)AsyncMigration with both prepare and revert methods@Sendable on all route handler closures@unchecked Sendable (Fluent requirement)autoMigrate in production (run migrations explicitly)revert in migrations (always provide rollback)try! or fatalError in request handlersMyVaporApp/
├── Package.swift
├── Sources/
│ └── App/
│ ├── Controllers/ # RouteCollection implementations
│ │ ├── UserController.swift
│ │ └── AuthController.swift
│ ├── Models/ # Fluent models
│ │ ├── User.swift
│ │ └── Post.swift
│ ├── DTOs/ # Request/response types (Content + Validatable)
│ │ ├── UserDTO.swift
│ │ └── CreateUserRequest.swift
│ ├── Migrations/ # AsyncMigration implementations
│ │ ├── CreateUser.swift
│ │ └── CreatePost.swift
│ ├── Middleware/ # AsyncMiddleware implementations
│ │ ├── JWTAuthMiddleware.swift
│ │ └── AppErrorMiddleware.swift
│ ├── Services/ # Business logic (protocol + implementation)
│ │ ├── UserService.swift
│ │ └── EmailService.swift
│ ├── Extensions/
│ │ └── Request+Extensions.swift
│ ├── configure.swift # Database, middleware, JWT, Leaf setup
│ ├── routes.swift # Top-level route registration
│ └── entrypoint.swift # @main entry point
├── Tests/
│ └── AppTests/
│ ├── UserControllerTests.swift
│ └── AuthControllerTests.swift
├── Resources/
│ └── Views/ # Leaf templates
│ └── index.leaf
├── Public/ # Static files
│ ├── css/
│ └── js/
└── docker-compose.yml
Layer responsibilities:
Controllers/ -- HTTP routing only: parse request, call service, write responseServices/ -- Business logic, orchestration, domain rulesModels/ -- Fluent database models with property wrappersDTOs/ -- Request/response types with validation (Content + Validatable)Migrations/ -- Schema changes with prepare and revertMiddleware/ -- Cross-cutting: auth, error handling, CORS, logging// entrypoint.swift
import Vapor
import Logging
@main
enum Entrypoint {
static func main() async throws {
var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = try await Application.make(env)
do {
try await configure(app)
try await app.execute()
} catch {
app.logger.report(error: error)
try? await app.asyncShutdown()
throw error
}
}
}
// configure.swift -- database, middleware, migrations, routes
func configure(_ app: Application) async throws {
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
app.middleware.use(AppErrorMiddleware())
app.middleware.use(CORSMiddleware())
// Database (always from environment variables)
if let databaseURL = Environment.get("DATABASE_URL") {
try app.databases.use(.postgres(url: databaseURL), as: .psql)
} else {
app.databases.use(.postgres(
hostname: Environment.get("DB_HOST") ?? "localhost",
port: Environment.get("DB_PORT").flatMap(Int.init) ?? 5432,
username: Environment.get("DB_USER") ?? "vapor",
password: Environment.get("DB_PASSWORD") ?? "vapor",
database: Environment.get("DB_NAME") ?? "vapor_dev"
), as: .psql)
}
app.migrations.add(CreateUser())
if app.environment == .development { try await app.autoMigrate() }
try routes(app)
}
// routes.swift -- group public vs protected
func routes(_ app: Application) throws {
app.get("health") { _ -> HTTPStatus in .ok }
let api = app.grouped("api", "v1")
try api.register(collection: AuthController())
let protected = api.grouped(JWTAuthMiddleware())
try protected.register(collection: UserController())
}
struct UserController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let users = routes.grouped("users")
users.get(use: index)
users.get(":userID", use: show)
users.put(":userID", use: update)
users.delete(":userID", use: delete)
}
@Sendable
func index(req: Request) async throws -> PaginatedResponse<UserResponse> {
let page = try req.query.decode(PageRequest.self)
let result = try await User.query(on: req.db)
.filter(\.$isActive == true)
.sort(\.$createdAt, .descending)
.paginate(PageRequest(page: page.page, per: page.per))
let items = try result.items.map { try UserResponse(user: $0) }
return PaginatedResponse(items: items, metadata: PageMetadata(
page: page.page, perPage: page.per,
total: result.metadata.total, totalPages: result.metadata.pageCount
))
}
@Sendable
func show(req: Request) async throws -> UserResponse {
guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
throw Abort(.notFound, reason: "User not found")
}
return try UserResponse(user: user)
}
}
Conventions: Implement RouteCollection per resource. Use @Sendable on all handlers. Validate before processing. Return DTOs, not Fluent models.
import Fluent
import Vapor
final class User: Model, Content, @unchecked Sendable {
static let schema = "users"
@ID(key: .id)
var id: UUID?
@Field(key: "email")
var email: String
@Field(key: "password_hash")
var passwordHash: String
@Enum(key: "role")
var role: Role
@Timestamp(key: "created_at", on: .create)
var createdAt: Date?
@Timestamp(key: "updated_at", on: .update)
var updatedAt: Date?
@Children(for: \.$user)
var posts: [Post]
init() {}
init(id: UUID? = nil, email: String, passwordHash: String, role: Role = .user) {
self.id = id
self.email = email
self.passwordHash = passwordHash
self.role = role
}
enum Role: String, Codable, CaseIterable {
case admin, user, guest
}
}
Model conventions:
final class conforming to Model, Content, @unchecked Sendable@ID(key: .id) for UUID primary keys@Timestamp for created_at and updated_at@Parent/@Children/@Siblings for relationshipsinit() (Fluent requirement)import Fluent
struct CreateUser: AsyncMigration {
func prepare(on database: Database) async throws {
let role = try await database.enum("user_role")
.case("admin").case("user").case("guest")
.create()
try await database.schema("users")
.id()
.field("email", .string, .required)
.field("password_hash", .string, .required)
.field("role", role, .required)
.field("created_at", .datetime)
.field("updated_at", .datetime)
.unique(on: "email")
.create()
}
func revert(on database: Database) async throws {
try await database.schema("users").delete()
try await database.enum("user_role").delete()
}
}
Migration rules:
prepare and revertrevert after deleting the table.references() for foreign keys with onDelete behaviorimport Vapor
struct CreateUserRequest: Content, Validatable {
let email: String
let password: String
let name: String
static func validations(_ validations: inout Validations) {
validations.add("email", as: String.self, is: .email)
validations.add("password", as: String.self, is: .count(8...))
validations.add("name", as: String.self, is: !.empty)
}
}
struct UserResponse: Content {
let id: UUID
let email: String
let name: String
let role: User.Role
let createdAt: Date?
init(user: User) throws {
self.id = try user.requireID()
self.email = user.email
self.name = user.name
self.role = user.role
self.createdAt = user.createdAt
}
}
DTO conventions:
Content + ValidatableContent onlytry CreateUserRequest.validate(content: req)Validatable rules: .email, .count(range), !.empty, .url, .alphanumericimport Vapor
import JWT
struct JWTAuthMiddleware: AsyncMiddleware {
func respond(
to request: Request,
chainingTo next: any AsyncResponder
) async throws -> Response {
guard let token = request.headers.bearerAuthorization?.token else {
throw Abort(.unauthorized, reason: "Missing authorization token")
}
let payload = try await request.jwt.verify(token, as: UserPayload.self)
guard let userID = UUID(payload.subject.value),
let user = try await User.find(userID, on: request.db),
user.isActive else {
throw Abort(.unauthorized, reason: "User not found or inactive")
}
request.auth.login(user)
return try await next.respond(to: request)
}
}
import Vapor
struct AppErrorMiddleware: AsyncMiddleware {
func respond(
to request: Request,
chainingTo next: any AsyncResponder
) async throws -> Response {
do {
return try await next.respond(to: request)
} catch let abort as AbortError {
let body = ErrorResponse(error: true, reason: abort.reason, code: abort.status.code)
return try await body.encodeResponse(status: abort.status, for: request)
} catch {
request.logger.error("Unexpected error: \(error)")
let reason = request.application.environment.isRelease
? "An internal error occurred" : error.localizedDescription
let body = ErrorResponse(error: true, reason: reason, code: 500)
return try await body.encodeResponse(status: .internalServerError, for: request)
}
}
}
struct ErrorResponse: Content {
let error: Bool
let reason: String
let code: UInt
}
Vapor's Content protocol handles JSON automatically. Configure custom encoding in configure.swift:
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .iso8601
ContentConfiguration.global.use(encoder: encoder, for: .json)
// configure.swift -- register signing key
guard let jwtSecret = Environment.get("JWT_SECRET") else {
fatalError("JWT_SECRET environment variable not set")
}
await app.jwt.keys.add(hmac: HMACKey(from: jwtSecret), digestAlgorithm: .sha256)
// Payload definition
struct UserPayload: JWTPayload {
var subject: SubjectClaim
var expiration: ExpirationClaim
var isAdmin: Bool?
func verify(using algorithm: some JWTAlgorithm) throws {
try expiration.verifyNotExpired()
}
}
// Token generation (in login handler)
let payload = UserPayload(
subject: .init(value: try user.requireID().uuidString),
expiration: .init(value: Date().addingTimeInterval(3600))
)
let token = try await req.jwt.sign(payload)
# Initialize project
swift package init --type executable --name MyVaporApp
# Resolve dependencies
swift package resolve
# Build and run
swift build
swift run App serve --hostname 0.0.0.0 --port 8080
# Run tests
swift test
swift test --filter AppTests
# Run database migrations manually
swift run App migrate
swift run App migrate --revert
# Docker build
docker build -t my-vapor-app .
docker compose up -d
| Package | Purpose |
|---------|---------|
| vapor/vapor | Core web framework |
| vapor/fluent | ORM abstraction |
| vapor/fluent-postgres-driver | PostgreSQL support |
| vapor/fluent-sqlite-driver | SQLite (development/testing) |
| vapor/redis | Redis caching and sessions |
| vapor/jwt | JWT authentication |
| vapor/leaf | Template engine |
| XCTVapor | Testing utilities (included with Vapor) |
For detailed patterns, WebSocket integration, Leaf templates, queues, testing, and deployment, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.