GP89
GP89

Reputation: 6730

Supporting api interface and web front end interface with shared logic using Vapor

I'm putting together authentication using vapor, and wondered if there's a good way to re-use logic in the routing to be used over rest api as well as a web front end.

struct AuthenticationController: RouteCollection {

    func boot(routes: RoutesBuilder) throws {
        routes.group("auth") { auth in
            auth.post("register", use: register)
            auth.post("login", use: login)
        }
        auth.group("email-verification") { emailVerificationRoutes in
            emailVerificationRoutes.post("", use: sendEmailVerification)
            emailVerificationRoutes.get("", use: verifyEmail)
        }
        ...
    }

    private func register(_ req: Request) throws -> EventLoopFuture<HTTPStatus> {
        ...
    }

    private func login(_ req: Request) throws -> EventLoopFuture<LoginResponse> {
        ...
    }

    ...

}

I'm then adding this auth collection under api

app.group("api") { api in
    try! api.register(collection: AuthenticationController())
}

All the return values are EventLoopFutures of either HTTPStatus when there's nothing to return, or structs that implement Content to return objects as json

What I'm wondering is how would be a good way to refactor out and resuse anything in auth and apply html web interfaces for it as well? So web forms for the post bits, pages for get, error responses etc. that could be served under /auth/login etc. as well as /api/auth/login

I was thinking I might be able to flatMap or similar the EventLoopFutures returned in the methods, and convert those by returning Views but I'm not sure how I'd go about that.

Or would it be a case of just pulling out all the logic from AuthenticationController, and making a WebAuthenticationController route collection for example that builds similar routes with the handlers in both controllers calling out to the logic handlers?

I've tried searching but wasn't able to find any examples of reusing handlers for multiple purposes

Upvotes: 1

Views: 70

Answers (1)

RelativeJoe
RelativeJoe

Reputation: 5084

Well you could create a new struct conforming to ResponseEncodable like this:

struct MixedResponse: ResponseEncodable {
    var view: View?
    var response: Response?
    static func view(_ view: View) -> MixedResponse {
        MixedResponse(view: view)
    }
    static func response(_ response: Response) -> MixedResponse {
        MixedResponse(response: response)
    }
}

Then in your encodeResponse function check what is nill & return the relevant data:

func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
    if let view {
        //do something
    }else if let response {
        //do something
    }
}

Instead of returning a specific response, you just return MixedResponse. I highly encourage you to try async/await instead of Futures.

Upvotes: 1

Related Questions