Group Group Group Group Group Group Group Group Group

Asynchronous data composition in API response


#1

Is there a way to perform 3 asynchronous database queries on a single route handler and return a single response like so:

{
	"id": 1,
	"short": "OMG",
	"long": "oh my god",
	"user": {
		"id": "725989A7-56A6-42F0-AF0E-F5A43AB29AC5",
		"name": "Admin",
		"username": "admin"
	},
	"categories": [{
		"id": 1,
		"name": "Teenagers"
	}]
}

as opposed to making multiple api calls:

GET http://localhost:8080/api/acronyms/1
GET http://localhost:8080/api/acronyms/1/categories
GET http://localhost:8080/api/acronyms/1/user

Is there a sample code example which I can take reference?


#2

@0xtim Can you please help with this when you get a chance? Thank you - much appreciated! :]


#3

@michaeltansg I’ve added a new chapter to the book that will cover this when it gets released, but in short, what you need to do is create a new type AcronymWithUserAndCategories that conforms to Content and then perform the queries to get that in your route handle and instantiate it with the data. If you look at how some of the Leaf routes do it, that should provide you with some inspiration!


#4

Correct me if I am wrong, but I believe you are referring to this piece of code?

  func acronymHandler(_ req: Request) throws -> Future<View> {
    return try req.parameters.next(Acronym.self)
      .flatMap(to: View.self) { acronym in
        return acronym.user
          .get(on: req)
          .flatMap(to: View.self) { user in
            let categories = try acronym.categories.query(on: req).all()
            let context = AcronymContext(title: acronym.short,
                                         acronym: acronym,
                                         user: user,
                                         categories: categories)
            return try req.view().render("acronym", context)
        }
    }
  }

I would like to get the acronym first and then fire off 2 asynchronous database requests:

  • one to fetch the user
  • the other fetches the categories

#5

Yeah that’s it. So your code would look something like:

func getAcronymWithUserAndCategories(_ req: Request) throws -> Future<AcronymWithUserAndCategories> {
  return try req.parameters.next(Acronym.self).flatMap(to: AcronymWithUserAndCategories) { acronym in
    return map(to: AcronymWithUserAndCategories.self), 
               acronym.user.get(on: req), 
               acronym.categories.query(on: req).all()) { user, categories in
      return AcronymWithUserAndCateogries(...)
    }
  }
}

#6

I just figured something very similar to this. Thanks for confirming @0xtim. (You might be missing a try before the map command though.

By the way, do you have a suggestion on how the URL path should be? Is there a good practice to follow here?


#7

Yeah quite likely missing a try - was written by hand :laughing:

As for the URL - it’s completely up to and depends who’s going to use it. There aren’t really any hard and fast guidelines for it, something like /acronyms/<ID>/withUserAndCategories would make sense!


#8

Thanks all, i fixed code and it work for me, correct me if have anything wrong.

image


#9

Yep looks good to me!