Full-stack Swift — aynı dil, aynı tipler, client ve server'da. Vapor, Swift'in en popüler server-side framework'üdür. Node.js veya Django'ya alternatif olarak type-safe, performanslı backend API'lar geliştirmeni sağlar. En güzel yanı: iOS app'indeki model'leri backend'de de kullanabilirsin.
💡 Hızlı Not: Vapor 4.0+ async/await destekler. Swift on Server ekosistemi Apple'ın Swift Server Work Group (SSWG) tarafından yönetilir.
İçindekiler
- Vapor Kurulumu
- Routing ve Controller'lar
- Fluent ORM ile Veritabanı
- JWT Authentication
- Middleware
- WebSocket Desteği
- iOS-Backend Model Paylaşımı
- Testing
- Docker ile Deployment
- Performance Benchmarks
Vapor Kurulumu {#kurulum}
bash
1# Vapor toolbox kur2brew install vapor3 4# Yeni proje oluştur5vapor new MyAPI -n6 7# Çalıştır8cd MyAPI9swift run10# Server running on http://127.0.0.1:8080Routing ve Controller'lar {#routing}
swift
1import Vapor2 3// routes.swift4func routes(_ app: Application) throws {5 // Basit route6 app.get("hello") { req in7 "Hello, World!"8 }9 10 // Route parametre11 app.get("users", ":userID") { req -> User in12 guard let id = req.parameters.get("userID", as: UUID.self) else {13 throw Abort(.badRequest)14 }15 guard let user = try await User.find(id, on: req.db) else {16 throw Abort(.notFound)17 }18 return user19 }20 21 // Controller kullanımı22 try app.register(collection: ProductController())23}24 25// ProductController.swift26struct ProductController: RouteCollection {27 func boot(routes: RoutesBuilder) throws {28 let products = routes.grouped("api", "v1", "products")29 products.get(use: index)30 products.post(use: create)31 products.group(":productID") { product in32 product.get(use: show)33 product.put(use: update)34 product.delete(use: delete)35 }36 }37 38 func index(req: Request) async throws -> [ProductDTO] {39 let products = try await Product.query(on: req.db).all()40 return products.map { $0.toDTO() }41 }42 43 func create(req: Request) async throws -> ProductDTO {44 let input = try req.content.decode(CreateProductRequest.self)45 let product = Product(name: input.name, price: input.price)46 try await product.save(on: req.db)47 return product.toDTO()48 }49 50 func show(req: Request) async throws -> ProductDTO {51 guard let product = try await Product.find(req.parameters.get("productID"), on: req.db) else {52 throw Abort(.notFound)53 }54 return product.toDTO()55 }56 57 func update(req: Request) async throws -> ProductDTO {58 guard let product = try await Product.find(req.parameters.get("productID"), on: req.db) else {59 throw Abort(.notFound)60 }61 let input = try req.content.decode(UpdateProductRequest.self)62 product.name = input.name ?? product.name63 product.price = input.price ?? product.price64 try await product.save(on: req.db)65 return product.toDTO()66 }67 68 func delete(req: Request) async throws -> HTTPStatus {69 guard let product = try await Product.find(req.parameters.get("productID"), on: req.db) else {70 throw Abort(.notFound)71 }72 try await product.delete(on: req.db)73 return .noContent74 }75}Fluent ORM ile Veritabanı {#fluent}
swift
1import Fluent2import FluentPostgresDriver3 4// Model5final class Product: Model, Content {6 static let schema = "products"7 8 @ID(key: .id)9 var id: UUID?10 11 @Field(key: "name")12 var name: String13 14 @Field(key: "price")15 var price: Double16 17 @Field(key: "is_active")18 var isActive: Bool19 20 @Timestamp(key: "created_at", on: .create)21 var createdAt: Date?22 23 @Parent(key: "category_id")24 var category: Category25 26 init() {}27 28 init(name: String, price: Double, categoryID: UUID) {29 self.name = name30 self.price = price31 self.isActive = true32 self.$category.id = categoryID33 }34}35 36// Migration37struct CreateProduct: AsyncMigration {38 func prepare(on database: Database) async throws {39 try await database.schema("products")40 .id()41 .field("name", .string, .required)42 .field("price", .double, .required)43 .field("is_active", .bool, .required, .custom("DEFAULT TRUE"))44 .field("created_at", .datetime)45 .field("category_id", .uuid, .required, .references("categories", "id"))46 .create()47 }48 49 func revert(on database: Database) async throws {50 try await database.schema("products").delete()51 }52}JWT Authentication {#auth}
swift
1import JWT2 3// JWT Payload4struct UserPayload: JWTPayload {5 var sub: SubjectClaim6 var exp: ExpirationClaim7 var role: String8 9 func verify(using signer: JWTSigner) throws {10 try exp.verifyNotExpired()11 }12}13 14// Auth middleware15struct JWTAuthMiddleware: AsyncMiddleware {16 func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {17 let payload = try request.jwt.verify(as: UserPayload.self)18 request.auth.login(payload)19 return try await next.respond(to: request)20 }21}22 23// Login endpoint24app.post("auth", "login") { req -> TokenResponse in25 let input = try req.content.decode(LoginRequest.self)26 guard let user = try await User.query(on: req.db)27 .filter(\.$email == input.email).first(),28 try user.verify(password: input.password) else {29 throw Abort(.unauthorized)30 }31 32 let payload = UserPayload(33 sub: .init(value: user.id!.uuidString),34 exp: .init(value: Date().addingTimeInterval(3600)),35 role: user.role36 )37 let token = try req.jwt.sign(payload)38 return TokenResponse(token: token)39}40 41// Protected route42let protected = app.grouped(JWTAuthMiddleware())43protected.get("me") { req -> User in44 let payload = try req.auth.require(UserPayload.self)45 guard let user = try await User.find(UUID(payload.sub.value), on: req.db) else {46 throw Abort(.notFound)47 }48 return user49}Middleware {#middleware}
swift
1// CORS middleware2app.middleware.use(CORSMiddleware(configuration: .init(3 allowedOrigin: .all,4 allowedMethods: [.GET, .POST, .PUT, .DELETE],5 allowedHeaders: [.accept, .authorization, .contentType]6)))7 8// Rate limiting middleware9struct RateLimitMiddleware: AsyncMiddleware {10 let maxRequests: Int11 let window: TimeInterval12 13 func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {14 let ip = request.remoteAddress?.ipAddress ?? "unknown"15 let key = "rate_limit:\(ip)"16 let count = try await request.cache.get(key, as: Int.self) ?? 017 18 guard count < maxRequests else {19 throw Abort(.tooManyRequests)20 }21 22 try await request.cache.set(key, to: count + 1, expiresIn: .seconds(Int(window)))23 return try await next.respond(to: request)24 }25}WebSocket Desteği {#websocket}
swift
1app.webSocket("ws", "chat") { req, ws in2 ws.onText { ws, text in3 // Broadcast to all connected clients4 for client in ChatRoom.shared.clients {5 client.send(text)6 }7 }8 ws.onClose.whenComplete { _ in9 ChatRoom.shared.remove(ws)10 }11 ChatRoom.shared.add(ws)12}iOS-Backend Model Paylaşımı {#model-sharing}
swift
1// SharedModels SPM paketi - iOS ve Vapor'da kullanılır2// Package.swift3let package = Package(4 name: "SharedModels",5 products: [.library(name: "SharedModels", targets: ["SharedModels"])],6 targets: [.target(name: "SharedModels")]7)8 9// SharedModels/Product.swift10public struct ProductDTO: Codable, Sendable {11 public let id: UUID12 public let name: String13 public let price: Double14 15 public init(id: UUID, name: String, price: Double) {16 self.id = id; self.name = name; self.price = price17 }18}Testing {#testing}
swift
1@testable import App2import XCTVapor3 4final class ProductTests: XCTestCase {5 func testCreateProduct() async throws {6 let app = Application(.testing)7 defer { app.shutdown() }8 try configure(app)9 10 try app.test(.POST, "api/v1/products",11 beforeRequest: { req in12 try req.content.encode(CreateProductRequest(name: "Test", price: 9.99))13 },14 afterResponse: { res in15 XCTAssertEqual(res.status, .ok)16 let product = try res.content.decode(ProductDTO.self)17 XCTAssertEqual(product.name, "Test")18 }19 )20 }21}Docker ile Deployment {#deployment}
dockerfile
1FROM swift:5.9-jammy as build2WORKDIR /app3COPY . .4RUN swift build -c release5 6FROM swift:5.9-jammy-slim7COPY --from=build /app/.build/release/App /app/App8EXPOSE 80809ENTRYPOINT ["/app/App", "serve", "--env", "production", "--hostname", "0.0.0.0"]Performance Benchmarks {#benchmarks}
Framework | Requests/sec | Latency (p99) | Memory |
|---|---|---|---|
Vapor (Swift) | 85,000 | 2.1ms | 45MB |
Express (Node) | 42,000 | 4.8ms | 120MB |
FastAPI (Python) | 28,000 | 8.2ms | 85MB |
Rails (Ruby) | 12,000 | 15ms | 200MB |
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var: **Kaynaklar:** - [Vapor Documentation](https://docs.vapor.codes/) - [Swift Server Work Group](https://www.swift.org/sswg/) - [Fluent ORM](https://docs.vapor.codes/fluent/overview/)

