Hot questions for Using Vapor in api

Question:

In the Vapor framework for server side swift, I would like to respond to a request with info I got from third party API. For example, I receive a get request asking for the temperature of a city, and I want to connect to yahoo whether API to get the temperature then send it back. Do I need to download packages like Alamofire? Or Is there a built in way to do so in Vapor?


Answer:

There is a built-in HTTP client in Vapor; it is called Client.

To make a GET request to your third party API:

let apiResponse = try drop.client.get("https://api.com")

You can pass your query parameters in the query string, or using the convenient dictionary method:

let apiResponse = try drop.client.get("https://api.com", query: ["q": queryString])

Client also supports POST, or any other HTTP method.

Question:

I am learning Vapor, and as part of this process I am building a website using the framework. As part of my application, I would like to make use of the Uber API which makes use of OAuth.

The process of retrieving a User Access Token as outlined here and it specifically recommends making use of a pre-built library to preform the authorization grant and token exchanges.

For OAuth, I was able to find two libraries, being:

OAuthSwift and p2/OAuth2

Both libraries don't seem to be compatible with Swift's Package Manager though and as a result, can't be used with Vapor.

Is there any work around to this? Or would I have to manually create a library to support the OAuth process with Vapor?


Answer:

From what I've seen most authentication in Vapor is done with turnstile: https://github.com/stormpath/Turnstile

There is good tutorial / walkthrough from raywenderlich: https://videos.raywenderlich.com/screencasts/637-server-side-swift-with-vapor-authentication-with-turnstile

Since you want to integrate with Uber, I suggest reading up on Uber's SDK for authentication and adding your own methods for handling the user endpoints, that way you can add Uber as a provider rather than adding an entirely new OAuth library / framework to do it for you.

Another great resource is Vapor OAuth, its beautifully written but a bit more technical: https://github.com/brokenhandsio/vapor-oauth

If you are new web development I suggest learning a well known framework such as React or Angular with Node.js before learning Vapor or have some native swift experience under your belt before continuing. React and Angular are Javascript based web frameworks that you can very easily get help with. React also supports iOS development with React Native and is used far more often than Vapor.

Question:

I'd like to make a post call with some parameters in Vapor 3.

POST: http://www.example.com/example/post/request

title: How to make api call
year: 2019

Which package/function can be used?


Answer:

It's easy, you could do that using Client like this

func thirdPartyApiCall(on req: Request) throws -> Future<Response> {
    let client = try req.client()
    struct SomePayload: Content {
        let title: String
        let year: Int
    }
    return client.post("http://www.example.com/example/post/request", beforeSend: { req in
        let payload = SomePayload(title: "How to make api call", year: 2019)
        try req.content.encode(payload, as: .json)
    })
}

or e.g. like this in boot.swift

/// Called after your application has initialized.
public func boot(_ app: Application) throws {    
    let client = try app.client()
    struct SomePayload: Content {
        let title: String
        let year: Int
    }
    let _: Future<Void> = client.post("http://www.example.com/example/post/request", beforeSend: { req in
        let payload = SomePayload(title: "How to make api call", year: 2019)
        try req.content.encode(payload, as: .json)
    }).map { response in
        print(response.http.status)
    }
}

Question:

Suppose I have a model called Estimate. I have a Vapor 3 API that I want to return a list of these models, filtered by query parameters. Doing so currently returns a Future<[Estimate]>, which results in the API returning JSON that looks like this:

[{estimate object}, {estimate object}, ...]

Instead, I'd like make it return something this:

{"estimates": [{estimate object}, {estimate object}, ...]}

So, the same thing as before, but wrapped in a JSON object with a single key, "estimates".

According to the documentation, any time I want to return something non-default, I should make a new type for it; this suggests to me I should create a type like:

final class EstimatesResponse: Codable {
  var estimates: [Estimate]?
}

However, after filtering I get a Future<[Estimate]> and NOT a pure [Estimate] array, meaning that I couldn't assign it to my EstimatesResponse estimates property. It seems weird to make the type of estimates be Future<[Estimate]>, and I'm not sure how that'd turn out.

How, then, can I return JSON of the correct format?


Answer:

First, you need to create Codable object, I prefer struct as below. Must implement protocol Content for routing.

struct EstimatesResponse: Codable {
  var estimates: [Estimate]
 }

 extension EstimatesResponse: Content { } 

I assumed that you are using a controller and inside the controller, you can use the following pseudo-code. Adjust your code so that val is Future<[Estimate]>, then use flatmap/map to get [Estimate].

func  getEstimates(_ req: Request) throws -> Future<EstimatesResponse> {
        let val = Estimate.query(on: req).all()
        return val.flatMap { model in
            let all = EstimatesResponse(estimates: model)
            return Future.map(on: req) {return all }
        }
    }

Question:

I'm developing a simple web API with Vapor. To give more context, I'm newbie in backend development.

The consumer of the API is going to be an iOS app. Currently, I don't need the users to sign up to use the app. And I would like to keep it like that.

On the other hand, I would like to have some authentication to avoid that anyone could use the API I'm developing.

Looking for information I've found how implement authentication. But the examples I've seen are based on creating users in the backend for each user of the app. What I don't want to do. I would like to use an api-key as we do normally when we use third-party api's.

How could I have "api-key authentication" with Vapor ??

Or, should I just create an unique user/password that it's shared by all the users of the iOS app (that use the API) and then use basic or token authentication?

Thank you very much!

Carlos


Answer:

One way around this is to create a fake token and use either the TokenAuthenticationMiddleware or more likely a custom middleware that checks the incoming token.

However, be aware that there is nothing stopping anyone from inspecting the traffic coming from your app to view the token and then using that to access your API.

Question:

So I'm trying to call a second endpoint in one of my Vapor endpoints. I have one endpoint that is just a get and works good:

router.get("status") { req -> Future<ConnectionResponse> in
  let client = try req.make(Client.self)
  let response = client.get("https://url.com/endpoint/")

  return response.flatMap(to: ConnectionResponse.self, { response in
    return try response.content.decode(ConnectionResponse.self)
  })
}

This returns correctly a ConnectionResponse json. When I try to do the same, but in a POST that needs some parameters, I can't figure out why the compiler doesn't let me run:

router.post("purchase") { req -> Future<ConnectionResponse> in
  return try req.content.decode(PurchaseRequest.self).map(to: ConnectionResponse.self, { response in
    let client = try req.make(Client.self)
    let response = client.get("https://url.com/endpoint/")

    return response.flatMap(to: ConnectionResponse.self, { response in
      return try response.content.decode(ConnectionResponse.self)
    })
  })
}

It fails on the flatMap saying Cannot convert return expression of type 'EventLoopFuture<ConnectionResponse>' to return type 'ConnectionResponse'.

As you can see, the purchase GET call is the same as the status apart from the initial decoding of the POST parameters. What am I doing wrong?


Answer:

Simply replace

return try req.content.decode(PurchaseRequest.self).map(to: ConnectionResponse.self, { response in

with

return try req.content.decode(PurchaseRequest.self).flatMap(to: ConnectionResponse.self, { response in

Complete working code may look like this

router.post("purchase") { req -> Future<ConnectionResponse> in
  return try req.content.decode(PurchaseRequest.self).flatMap { response in
    let client = try req.make(Client.self)
    let response = client.get("https://url.com/endpoint/")

    return response.flatMap { response in
      return try response.content.decode(ConnectionResponse.self)
    }
  }
}

So what is the difference between map and flatMap?

flatMap is used if next result is Future<Something> e.g.:

someDatabaseCall(on: container).flatMap { databaseResult1 in
    /// it will return Future<DatabaseResult>
    /// that's why we use `flatMap` above instead of `map`
    return anotherDatabaseCall(on: container) 
}

map is for non-future results e.g.:

someDatabaseCall(on: container).map { databaseResult1 in
    /// it will return non-future result
    /// that's we use `map` above instead of `flatMap`
    return "hello world" // just simple string
}

What is Future<> ? It's just a promise, like an object with callback.

Question:

I have a Vapor API that has a route to register users. This route recibes a nested object in JSON format like this:

{
    "name": "Test",
    "email": "test1@test.com",
    "password": "Test1",
    "phone": {
        "numberString": "+52 999 999 9999",
        "countryCode": 52,
        "nationalNumber": 9999999999,
    }
}

This JSON is converted into a Content/Codable Object:

final class Create: Codable, Content {
    let name: String!
    let email: String!
    let password: String
    let phone: PhoneNumber!

    init(name: String, email: String, password: String, phone: PhoneNumber) {
        self.name = name
        self.email = email
        self.password = password
        self.phone = phone
    }
}

I have tried this route sending the JSON string as raw via Postman and the route worked perfectly but the problem is when I try to send it via URLSession in my iOS counterpart the ErrorMiddleware throws a DecodingError:

DecodingError: Value of type 'String' required for key 'password'.

At first I thought that the problem was the JSON generation until, for test purpose I send the same JSON as in the example and the Vapor API is still throwing the Error.

let urlStr = "\(BaseURL)/api/student/register"

guard let url = URL(string: urlStr) else { return }

var urlRequest = URLRequest(url: url, cachePolicy:
    .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30.0)


let raw = "{\"name\":\"Prueba\",\"email\":\"prueba1@hotmail.com\",\"password\":\"Prueba1\",\"phone\":{\"numberString\":\"+52 999 271 5671\",\"countryCode\":52,\"nationalNumber\":9992715671}}"

urlRequest.httpMethod = "POST"
urlRequest.httpBody = raw.data(using: .utf8)

urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")

URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
    ...
}.resume()

Can you even send this types of JSON's via URLSession or do I need to change my logic so it will receive a flat array?


Answer:

After hours of debugging and getting strange errors I realized that my error was simpler than I thought. The error was:

{"error":true, "reason":"Value of type 'String' required for key 'password'."}

And I tried to send in Postman a request with out the key 'password' which returned:

{"error": true, "reason": "Value required for key 'password'."}

What got me to analyze my objects and then I saw the error, my Create object wasn't unwrapped correctly, more precisely this:

let password: String

Should be having a ! next to String like this.

let password: String!

The reason why this worked on Postman and not on URLSession is still uncleared.

UPDATE:

As proposed by @vadian the headers in this URLSession are also missing and even tho after I added the force unwrapped the API passed the request but with nil content

 urlRequest.setValue("application/json", forHTTPHeaderField:"Accept")
 urlRequest.setValue("application/json", forHTTPHeaderField:"Content-Type")

Question:

Disclaimer: this question originally asked on the Vapor help channel on Slack

Easy question: if I'm sending a response like this

let response: ResponseRepresentable = try JSON(node: ["message": "User Created"])`

How can i set the status code for the response?


Answer:

You can use the Response class directly, like this:

let statusCode = Status.other(statusCode: 666, reasonPhrase: "damn it")
let response = Response(status: statusCode, json: JSON(["error": "my error"]))

See the Response Documentation for more info.

Question:

I'm new to software development in general, and I'm writing a backend for a simple ride-sharing iOS application (which I'll develop later). I'm using Vapor to create the backend.

When a user makes a trip request to the API, I want to create a new trip and establish a websocket session between the user and a driver. The problem I'm having is how can I notify a driver that a request is in and add him to the session?

Here's what I've come up with so far, although I'm not sure if it's going to work:

When a trip request comes in, I would create a session and a trip object with the session id. When a driver visits the "Trip requests" tab in the app he would make a get request to retrieve active trip requests. When he then clicks on one of the trip requests, he would make a request with the session id of that particular trip to be added to that session.

The problems I'm seeing with the above solution is that that would make the User the Poster, and the Driver the Observer, which I'm not sure is the way to go since I want the driver to act as a poster (to send location updates so that the user can track the driver on the map in real time).

Another problem is that users would have to wait indefinitely before a driver accepts their request.

Is there a better way to notify the driver of a trip request? How can I go about achieving this?


Answer:

First, you will get trouble when trying to establish a direct connection between user and driver, because it's quite difficult to directly connect to an app on a smartphone (changing IPs, NAT, firewalls and opening ports are some of the problems).

I'd suggest, you implement some kind of REST API for the trips/trip requests. To notify the driver or send updates back to the user, you can either use push notifications (for iOS it's APNS) or websockets. In the best case both for different situations.

Here are some hints for further research on those topics:

I hope that helps for your next steps. Your question is quite broad to be more specific.

Question:

'm pretty stuck after a few days of trying to get this working and I could use some help.

I have a vapor API that works fine. I created a route and can access it from http://localhost:8080/backend/returnA in a browser on the server. It returns some JSON.

Where I'm stuck is in trying to configure Nginx to server as a proxy. Can anyone help me understand how the http://localhost:8080/backend/returnA URL translates into a working URL accessible from the LAN?

I'm pretty confused as the Nginx.conf asks for a root URL but I don't know what to put in. If I leave it blank it defaults to /usr/local/Cellar/nginx/1.15.6/html/backend/returnA/index.html which obviously won't work. If I set it to the public folder in the Vapor app directory this also doesn't work. In both instances I get a "No such file or directory".

I've gone through countless Nginx conf settings found online, tried adding a proxy location, nothing works. Trying http://172.16.1.25/backend/returnA/ always returns a 404 from the Nginx server.

How do I point Nginx to my Vapor route when it's not serving a static file like index.html, and instead retuning JSON?

Any help is much appreciated.

Here's the config, edited to include Thanh's code, old location commented out:

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

    http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local]       "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';
    #access_log  logs/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    #keepalive_timeout  0;
    keepalive_timeout  65;
    #gzip  on;

    server {
    server_name 172.16.1.25;
    listen       80  default_server;

    root /Users/localadmin/Developer/server/MedicapAPI/Public/;

    # location @proxy {
    #    proxy_pass http://127.0.0.1:8080;
    #    proxy_pass_header Server;
    #    proxy_set_header Host $host;
    #    proxy_set_header X-Real-IP $remote_addr;
    #    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    #    proxy_pass_header Server;
    #    proxy_connect_timeout 3s;
    #    proxy_read_timeout 10s;
    # }

    location  / {
        proxy_ignore_client_abort on;
        proxy_pass http://localhost:8080/;
        proxy_redirect off;
    }    
    }
        include servers/*;
    }

Answer:

Using this :

listen       80  default_server;
server_name 172.16.1.25; #ip address of server

it will catch all server blokck and:

location  / {

                  proxy_ignore_client_abort on;
                  proxy_pass http://localhost:8080/;
                  proxy_redirect off;
}  

It will be proxy_pass to application which is running in port 8080

Question:

I am trying to call a 3rd Party API when a Client goes to a specific route using Swift and Vapor 3; however, I am met with errors despite being able to make the call.

I have taken steps to ensure the error is caused by the get request to the API. Currently, I do nothing with the request other than print the response. Removing the request stops the error from happening.

// Route the user to HomePage
router.get { req -> Future<View> in
    let token: String = "THATS_MY_TOKEN"
    let client = try req.client()
    let bcmsrequest = try client.get("https://api.buttercms.com/v2/posts/?page=1&page_size=10&auth_token=" + token)
    print(bcmsrequest)
    print("This line prints.")
    // Removing the above get request stops the error below from happening
    return try req.view().render("welcome")
}

// Here is the stdout printed by the server process:
Server starting on http://localhost:8080
NIO.EventLoopFuture<Vapor.Response>
This line prints.
2019-07-27 11:49:12.249196-0400 Run[6348:121267] [] nw_endpoint_get_type called with null endpoint
2019-07-27 11:49:12.249420-0400 Run[6348:121267] [] nw_endpoint_get_type called with null endpoint, dumping backtrace:
        [x86_64] libnetcore-1872
    0   libnetwork.dylib                    0x00007fff6d188fc8 __nw_create_backtrace_string + 120
    1   libnetwork.dylib                    0x00007fff6ce12af4 nw_endpoint_get_type + 180
    2   libboringssl.dylib                  0x00007fff6b6e3af2 nw_protocol_boringssl_get_subject_name + 178
    3   libboringssl.dylib                  0x00007fff6b6e6997 nw_protocol_boringssl_connected + 916
    4   libnetwork.dylib                    0x00007fff6ce5d145 nw_socket_handle_socket_event + 1733
    5   libdispatch.dylib                   0x00000001017fd82f _dispatch_client_callout + 8
    6   libdispatch.dylib                   0x0000000101800689 _dispatch_continuation_pop + 585
    7   libdispatch.dylib                   0x0000000101816608 _dispatch_source_invoke + 2135
    8   libdispatch.dylib                   0x0000000101807665 _dispatch_workloop_invoke + 3477
    9   libdispatch.dylib                   0x0000000101813025 _dispatch_workloop_worker_thread + 676
    10  libsystem_pthread.dylib             0x000000010188f343 _pthread_wqthread.cold.1 + 125
    11  libsystem_pthread.dylib             0x0000000101889196 _pthread_wqthread + 203
    12  libsystem_pthread.dylib             0x0000000101889057 start_wqthread + 15

I can see that a Future Object is being printed out, I would expect to see response content (a JSON string) - but I believe that the response has no content and is actually failing to make a request at all.


Answer:

I can see that a Future Object is being printed out, I would expect to see response content

So this is the crux of the problem. Because Vapor is asynchronous, you're printing the Future<Response> as soon as you send the request, meaning it hasn't returned yet. If you change it to:

router.get { req -> Future<View> in
    let token: String = "THATS_MY_TOKEN"
    let client = try req.client()
    let bcmsrequest = try client.get("https://api.buttercms.com/v2/posts/?page=1&page_size=10&auth_token=" + token)
    return bcmsrequest.flatMap { response in
        print(response)
        return try req.view().render("welcome")
    }
}

You'll see the full response body and see what errors you're getting (if any)

Question:

While learning how to use Swift and Vapor to create a REST api, while testing this API using postman, I am unable to make an API Post request, the error I receive is:

{
    "error": true,
    "reason": "Value of type 'String' required for key 'email'."  
}

I am using a PostgreSQL database reference the model. I feel something is wrong with my model.

Here is the model I am using:

final class Todo: PostgreSQLModel {
    var id: Int?
    var email: String
    var password: String
    var name_first: String
    var name_last: String
    var username: String

    init(id: Int?, email: String, name_first: String, name_last: String, username:String, password: String) {
        self.id = id
        self.email = email
        self.name_first = name_first
        self.name_last = name_last
        self.username = username
        self.password = password
    }
}

Answer:

It turns out I was using postman wrong. I was passing things through the header the body my mistake.

Question:

i noticed a very bad performance of Fluent when i run a query written in Swift in Vapor. When i run a raw query in this way:

 SELECT *  FROM "Model"

the time of response is about 20-30 ms. If i run the query in this way:

Model.query(on: request).all()

the time of response is increased tenfold. Why? What's the problem?


Answer:

A big chunk of the extra time there is likely to be decoding of the query result into Model objects.

Also work is being done on Vapor 4 (and the new Fluent version to go with it) which will make database queries a lot faster.