# Implementing Pipes2 Endpoints

### Introduction

Zapp apps consume data via *Pipes2 Endpoints*.

A *Pipes2 Endpoint* is a simple REST API that responds with a JSON that conforms to the *Pipes Feed JSON* schema. *Pipes2 Endpoints* can be developed in any programming language or framework.

A good implementation of *Pipes2 Endpoints* will result in consumer applications that are secure & efficient as well as increase the flexibility available for editors working in *Zapp Studio*.

:::info
Users of *Zapp Studio* can connect UI components to any *Pipes2 Endpoint*, the *Pipes2 Client* embedded in Zapp's applications will send the endpoint additional context about the request such as user/device-specific properties.
:::

:::tip
A *Pipes2 Endpoint* can be implemented as a middleware between the consumer applications and the backend system (i.e. CMS) or as an integral part of the backend system, for example as a wordpress plugin. When deciding about these options it's best to take into account the requirements regarding latency, scale and cost. It's often the case that legacy CMSs does not meet the scalability needed by consumer applications, for those cases Applicaster recommends to implement the *Pipes2 Endpoints* as a middleware on a layer that is more cost-effective to scale (for example serverless) and ensure the calls to the legacy system's API are cached.
:::

### Pipes2 Simple Endpoint Example

The following is a simple *Pipes2 Endpoint* implemented with [expressjs](https://expressjs.com/) that returns the
static [*Pipes Feed JSON*](./feed-json-protocol.md) that is stored in the `feed.json` file (shown below).

```javascript
const express = require('express')
const app = express()
const port = 8080;
const feed = fs.readFileSync('feed.json');

app.get('/example', (req, res) => {
  res
    .set('Content-Type', 'application/vnd+applicaster.pipes2+json')
    .send(feed)
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
```

```json
{
  "id": "https://www.example.com/path/of/example/endpoint/myFeedID",
  "type": { "value": "feed" },
  "title": "Example feed",
  "entry": [
    {
      "id": "89123456701",
      "title": "Example Video",
      "summary": "Action · 149 Mins",
      "updated": "2019-11-15T10:41:03+00:00",
      "published": "2019-11-15T10:31:00+00:00",
      "type": { "value": "video" },
      "content": {
        "src": "https://www.example.com/path/to/example/video.m3u8",
        "type": "video/hls"
      },
      "extensions": {
        "free": true,
        "section": "JUST ADDED",
        "duration": 9863
      },
      "media_group": [
        {
          "type": "image",
          "media_item": [
            {
              "key": "thumbnail",
              "src": "https://www.example.com/path/to/example/image.jpg",
            }
          ]
        }
      ]
    }
  ]
}
```

### Using the Path and Query Parameters

When a *Pipes2 Client* is instructed to fetch a feed from a given URL, the client will attempt to find a configured *Pipes2 Endpoint* from Zapp's data source configuration with an endpoint URL that is a prefix of the feed URL.

This means that implementations may choose to implement one endpoint for all types of data and rely on the path and query string to control the returned entries. The possible paths and query parameters are not governed by Zapp or the Pipes2 protocol except the `ctx` query parameter that is reserved for protocol usage.

For example, given an endpoint with the prefix of `https://example.com/pipes2`, a component can fetch data using the feed URL `https://example.com/pipes2/shows?page=1&limit=25` or by using `https://example.com/pipes2/shows/some-show/seasons?orderBy=index:asc`.
The following [expressjs](https://expressjs.com/) code uses both path and parameters to return the wanted result:

```javascript
const express = require('express');
const app = express();
const port = 8080;

app.get('/pipes2/shows', (req, res) => {
  res
    .set('Content-Type', 'application/vnd+applicaster.pipes2+json')
    .send(getAllShows(page: req.query.page, limit: req.query.limit));
})

app.get('/pipes2/shows/:showId/seasons', (req, res) => {
  res
    .set('Content-Type', 'application/vnd+applicaster.pipes2+json')
    .send(getSeasonsByShowId(req.params.showId, orderBy: req.query.orderBy));
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
```

### Using Context Parameters

*Pipes2 Clients* can send additional parameters to the *Pipes2 Endpoint* when instructed to do so. When an endpoint is configured for an application in Zapp it is configured with a list of local context parameters to be sent for the endpoint (*Context Keys*).
These parameters will be sent to the endpoint in the `ctx` query string parameter.
The value of `ctx` will be a `base64URL` encoded JSON containing an object whose keys are the *Context Keys* and their respective values in the application's context are the values.

> To find out more about `base64URL` please refer to https://tools.ietf.org/html/rfc4648#section-5

Context parameters are provided by the Zapp SDK and the plugins that are activated in the application.
See [list of builtin context parameters](./available-context-keys.md)

For example, an endpoint that uses the `deviceId` context parameter to customize the feed:

```javascript
const express = require('express')
const base64url = require('base64url')
const app = express()
const port = 8080;

app.get('/recently-watched', (req, res) => {
  const context = JSON.parse(base64url.decode(req.query.ctx))
  res
    .set('Content-Type', 'application/vnd+applicaster.pipes2+json')
    .send(generateFeedByTimezoneOffset(context["timeZoneOffset"]))
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
```

#### Geo Restricting content with `signedDeviceInfoToken` context key

A common use case is to be able serve / geo block specific content according to the user's location.
Zapp apps support this feature out of the box using `context keys`.

Once you set in Zapp the `signedDeviceInfoToken` context key on an Endpoint - a signed JWT context key will be added to each of the feeds' endpoints.

> **Note:** On native platforms (all Android-based, Apple, and Roku platforms), `signedDeviceInfoToken` is provided by the Applicaster DI Token plugin. If the plugin is not installed in your app, this key will be missing from the context. On WebTV platforms (e.g. Samsung TV, LG TV), it is built in and always available.

On your Pipes2 server implementation you'll be able to decode / validate the JWT token and use it to serve the right content to your user.

```javascript
const express = require('express')
const base64url = require('base64url')
// list of available JWT libraries can be found at - https://jwt.io/ 
const jwt = require('jsonwebtoken');
const app = express()
const port = 8080;

app.get('/recently-watched', async (req, res) => {
  // Base64 decode the ctx query param
  const context = JSON.parse(base64url.decode(req.query.ctx))
  // Get the signed Device JWT
  const signedDeviceInfoToken = context["signedDeviceInfoToken"];

  // Get the public key from `https://di.applicaster.com/public` and store it an environment variable
  const diPublicKey = process.env.DI_PUBLIC_KEY 
  const { country } = jwt.verify(signedDeviceInfoToken, diPublicKey);
  
 
  // Return a feed with one live stream - use a different stream to server clients from United States and other to server clients outside of United States

  res
    .set('Content-Type', 'application/vnd+applicaster.pipes2+json')
    .send({
      id: 'my-feed-id',
      entry: [
        {
          id: 'my-live-stream',
          title: 'Live Stream',
          content: {
            // US is the country code of the United States according to ISO 3166-1 (https://en.wikipedia.org/wiki/ISO_3166-1)
            src: (country === 'US') ? 'https://us-live-strem.m3u8' : 'https://rest-of-the-world-live-strem.m3u8'
            type: "video/hls"
          }
        }
      ]
    })
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
```

#### Implementing context keys for language selector
To support multi-language experience you'll need to add the language selector plugin.
When using this plugin users can choose their preferred language from a pre-defined list of set languages.

When a user choose a language, the selected language code (`languageCode`) is added to the list of the device context keys. You can use this context key to serve the response according to the selected language.

```
const express = require('express')
const base64url = require('base64url')
// list of available JWT libraries can be found at - https://jwt.io/ 
const jwt = require('jsonwebtoken');
const app = express()
const port = 8080;

const t = (languageCode) => {
  en: {
    title: "My English title"
  }
  es: {
    title "Mi titulo en español"
  }
}

app.get('/recently-watched', async (req, res) => {
  // Base64 decode the ctx query param
  const context = JSON.parse(base64url.decode(req.query.ctx))
  // Get the signed Device JWT
  const languageCode = context["languageCode"];

 
  res
    .set('Content-Type', 'application/vnd+applicaster.pipes2+json')
    .send({
      id: 'my-feed-id',
      entry: [
        {
          id: 'my-live-stream',
          // For brevity we show translation on the title but 
          // you can add it to any field
          title: t(languageCode).title,
        
        }
      ]
    })
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
```
```

:::tip
If you want to test your server implementation, you can use the [Pipes2 Feed JSON validator
](/integrations/tools-feed-validator):

- Add you feed URL in the URL section.
- Click on the `Add context key & value pair` link.
- Add the context key `signedDeviceInfoToken` on the `key`.
- Go to `https://di.applicaster.com/` and copy the value of the `jwt` key and paste it in the as the value of the context key.
- Click on the `Analyze` button - you'll be able to see the feed geo targeted to your location.
- In the case you need to test another location access the `https://di.applicaster.com/` from a VPN to simulate your desired country.

:::
