Tüm Yazılar
KategoriBackend
Okuma Süresi
23 dk
Yayın Tarihi
...
Kelime Sayısı
1.322kelime

Kahveni hazırla - bu içerikli bir makale!

Vapor framework ile RESTful API geliştirme. Fluent ORM, JWT auth, middleware, WebSocket, Docker deployment ve iOS-backend model paylaşımı.

Swift Vapor ile Backend API: Full-Stack Swift Geliştirme

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

  1. Vapor Kurulumu
  2. Routing ve Controller'lar
  3. Fluent ORM ile Veritabanı
  4. JWT Authentication
  5. Middleware
  6. WebSocket Desteği
  7. iOS-Backend Model Paylaşımı
  8. Testing
  9. Docker ile Deployment
  10. Performance Benchmarks

Vapor Kurulumu {#kurulum}

bash
1# Vapor toolbox kur
2brew install vapor
3 
4# Yeni proje oluştur
5vapor new MyAPI -n
6 
7# Çalıştır
8cd MyAPI
9swift run
10# Server running on http://127.0.0.1:8080

Routing ve Controller'lar {#routing}

swift
1import Vapor
2 
3// routes.swift
4func routes(_ app: Application) throws {
5 // Basit route
6 app.get("hello") { req in
7 "Hello, World!"
8 }
9 
10 // Route parametre
11 app.get("users", ":userID") { req -> User in
12 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 user
19 }
20 
21 // Controller kullanımı
22 try app.register(collection: ProductController())
23}
24 
25// ProductController.swift
26struct 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 in
32 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.name
63 product.price = input.price ?? product.price
64 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 .noContent
74 }
75}

Fluent ORM ile Veritabanı {#fluent}

swift
1import Fluent
2import FluentPostgresDriver
3 
4// Model
5final 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: String
13 
14 @Field(key: "price")
15 var price: Double
16 
17 @Field(key: "is_active")
18 var isActive: Bool
19 
20 @Timestamp(key: "created_at", on: .create)
21 var createdAt: Date?
22 
23 @Parent(key: "category_id")
24 var category: Category
25 
26 init() {}
27 
28 init(name: String, price: Double, categoryID: UUID) {
29 self.name = name
30 self.price = price
31 self.isActive = true
32 self.$category.id = categoryID
33 }
34}
35 
36// Migration
37struct 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 JWT
2 
3// JWT Payload
4struct UserPayload: JWTPayload {
5 var sub: SubjectClaim
6 var exp: ExpirationClaim
7 var role: String
8 
9 func verify(using signer: JWTSigner) throws {
10 try exp.verifyNotExpired()
11 }
12}
13 
14// Auth middleware
15struct 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 endpoint
24app.post("auth", "login") { req -> TokenResponse in
25 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.role
36 )
37 let token = try req.jwt.sign(payload)
38 return TokenResponse(token: token)
39}
40 
41// Protected route
42let protected = app.grouped(JWTAuthMiddleware())
43protected.get("me") { req -> User in
44 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 user
49}

Middleware {#middleware}

swift
1// CORS middleware
2app.middleware.use(CORSMiddleware(configuration: .init(
3 allowedOrigin: .all,
4 allowedMethods: [.GET, .POST, .PUT, .DELETE],
5 allowedHeaders: [.accept, .authorization, .contentType]
6)))
7 
8// Rate limiting middleware
9struct RateLimitMiddleware: AsyncMiddleware {
10 let maxRequests: Int
11 let window: TimeInterval
12 
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) ?? 0
17 
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 in
2 ws.onText { ws, text in
3 // Broadcast to all connected clients
4 for client in ChatRoom.shared.clients {
5 client.send(text)
6 }
7 }
8 ws.onClose.whenComplete { _ in
9 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ır
2// Package.swift
3let package = Package(
4 name: "SharedModels",
5 products: [.library(name: "SharedModels", targets: ["SharedModels"])],
6 targets: [.target(name: "SharedModels")]
7)
8 
9// SharedModels/Product.swift
10public struct ProductDTO: Codable, Sendable {
11 public let id: UUID
12 public let name: String
13 public let price: Double
14 
15 public init(id: UUID, name: String, price: Double) {
16 self.id = id; self.name = name; self.price = price
17 }
18}

Testing {#testing}

swift
1@testable import App
2import XCTVapor
3 
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 in
12 try req.content.encode(CreateProductRequest(name: "Test", price: 9.99))
13 },
14 afterResponse: { res in
15 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 build
2WORKDIR /app
3COPY . .
4RUN swift build -c release
5 
6FROM swift:5.9-jammy-slim
7COPY --from=build /app/.build/release/App /app/App
8EXPOSE 8080
9ENTRYPOINT ["/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/)

Etiketler

#vapor#swift#backend#api#server-side-swift#rest#full-stack
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz