Stream Hotel Rates

If you've ever experienced slow processing times when requesting large volumes of hotel rates, you're not alone. High-demand requests can consume significant resources, leading to delays in delivering valuable content to your users.

To address this, we've introduced Readable Streams to our v3.0/hotels/rates endpoint. With Readable Streams, you can begin processing hotel rates as they arrive, rather than waiting for the entire dataset. This approach reduces load times and minimizes resource spikes.

How to Stream Hotel Rates

Using the streaming feature is simple and requires minimal changes to your current setup. Our POST request structure remains unchanged, with the addition of an optional parameter: "stream": true. When this parameter is included, the API switches from delivering a single JSON response to streaming data incrementally.

Streaming Data Format

Here's how the data is structured during streaming:

  1. Data Prefix: Each fragment begins with data:
  2. Data Suffix: Each piece of data is separated by a new line \n\n.
  3. Types of JSON Objects:
    1. "rates": Contains hotel rates, structured as a data array in our standard format (see LiteAPI Travel API Reference). Depending on the search parameters, each stream line may contain multiple rates.
    2. "hotels": A summary of hotel information including hotel ID, name, and photo. This is sent at the end of the stream for ease of reading.
  4. Stream Termination: The end of the stream is marked by data: [DONE]. Then the API will close the connection.

Here you have an example of how Stream Hotel Rates look like

data: {"rates":[{"hotelId":"lp19d80","roomTypes":[{...}]},{"hotelId":"lp19d81","roomTypes":[{...}]}]} \n\n
data: {"rates":[{"hotelId":"lp19c91","roomTypes":[{...}]},{"hotelId":"lp19c92","roomTypes":[{...}]}]} \n\n
data: {"rates":[{"hotelId":"lp19eda","roomTypes":[{...}]},{"hotelId":"lp19edb","roomTypes":[{...}]}]} \n\n
data: {"hotels":[{"id":"lp19d80","name":"Resorts Casino Hotel ...},{"id":"lp19d81","name":"Resorts Hotel ...}]}  \n\n
data: [DONE] \n\n

Node.js (backend) + Javascript (frontend) example

This example explains how to consume the stream using an Express Node.js server and a Javascript function for the frontend in 2 steps.

  1. We are going to create a simple Express Server to stream the results to a frontend and protect the ApiKey. This server opens a POST endpoint and sends the post to LiteAPI, then the response is piped to the front as soon it arrives. It is required to replace YOUR_API_KEY with your LiteAPI Key (Use this link to have access to it in the API Key tab ).

    const express = require("express");
    const { join } = require("path");
    const expressStatic = express.static;
    const { pipeline } = require("stream");
    const app = express();
    const port = 3000;
    const apiKey = "YOUR_API_KEY";
    
    app.use(express.json());
    app.use(expressStatic(join(__dirname)));
    
    app.listen(port, () => {
      console.log(`Server is running on http://localhost:${port}`);
    });
    
    app.post("/stream-rates", async (req, res) => {
      try {
        const fetch = (await import("node-fetch")).default;
        const apiResponse = await fetch("https://api.liteapi.travel/v3.0/hotels/rates", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "X-API-Key": apiKey,
          },
          body: JSON.stringify(req.body),
        });
    
        if (!apiResponse.body) {
          res.status(500).send("Error: No response body from API");
          return;
        }
        res.setHeader("Content-Type", "text/event-stream");
    
        // Stream the data from the API to the client
        pipeline(apiResponse.body, res, (err) => {
          if (err) {
            console.error("Pipeline failed:", err);
            res.status(500).send("Internal Server Error");
          }
        });
        console.log("Stream closed");
      } catch (error) {
        console.error("Error fetching data from API:", error);
        res.status(500).send("Internal Server Error");
      }
    });
    
  2. Then with this simple javascript function, we could start to work.

    document.getElementById("fetchRates").addEventListener("click", async () => {
      try {
        const response = await fetch("/stream-rates", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            occupancies: [
              {
                adults: 2,
              },
            ],
            roomMapping: true,
            currency: "USD",
            guestNationality: "US",
            checkin: "2024-12-15",
            checkout: "2024-12-18",
            timeout: 15,
            countryCode: "US",
            limit: 500,
            stream: true,
          }),
        });
    
        const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
        let messageBuffer = "";
        while (true) {
          const { value, done } = await reader.read();
          if (done) break;
          messageBuffer += value;
          const messages = messageBuffer.split("\n\n");
    
          for (let i = 0; i < messages.length - 1; i++) {
            const message = messages[i];
            if (message.startsWith("data: ")) {
              const data = message.slice(6); // deletes "data: "
              if (data === "[DONE]") {
                console.log("Stream complete!");
                return;
              } else {
                dataJson = JSON.parse(data);
                if (dataJson.rates != null) {
                  console.log("hotelID: ", dataJson.rates[0].hotelId); // Access to the first rates HotelID of the current "rates chunk"
                }
                if (dataJson.hotels != null) {
                  console.log("name: %s, hotelID", dataJson.hotels[0].name, dataJson.hotels[0].id); // Access to the hotels name of the "hotels chunk"
                }
              }
            }
          }
          messageBuffer = messages[messages.length - 1];
        }
      } catch (error) {
        console.error(`Error: ${error.message}`);
      }
    });
    

In the provided example, JavaScript's fetch() function initiates an API request, and the response is received as a stream.

This stream is processed incrementally, allowing your application to start handling data as it arrives. Each chunk of data is then processed immediately, allowing your application to start working with the data without waiting for the entire response to be received. From each chunk, we remove the prefix data: and suffix \n\n, and then the text is parsed to JSON with dataJson = JSON.parse(data).

Finally, we added an if to access the data, depending on the current chunk processed which could be 2 kinds ratesor hotels. the process finished if the data contains a [DONE]