---
title: Products - Controller
description: List and retrieve `Product` models.
---

To implement the `Products` controller, you need to implement 2 API endpoints, we will use these endpoints to fetch products from your system.

## Prerequisites
::grid{cols="2"}
    ::card-baseline{title="Product Model" to="/integrations/models/product"}
      A code that can be redeemed for a discount.
      ::flex{class="mt-4"}
        :badge{label="Required" color="green"}
      ::
    ::
    ::card-baseline{title="Families - Controller" to="/integrations/controllers/families"}
      If you decided to implement products with families, you must implement a `Families` controller first.
      ::flex{class="mt-4"}
        :badge{label="Optional"}
      ::
    ::
::

## SDK
If you are using the SDK, you can implement the `Products` controller by following the code example below. You don't need to get into the details of the API endpoints, the SDK will take care of that for you.

::code-group
```typescript [Typescript]
import { Integrator } from '@churnkey/sdk'
import { Context } from '../Context'
import { Product } from '../models/Product'
// import { Families } from './Families'

export const Products = Integrator.Products.config({
    ctx: Context,
    type: Integrator.Product.Type.Standalone,
    // type: Integrator.Product.Type.Family,
    // Families: Families,
    async retrieve(ctx, options) {
        const yourProduct = await ctx.db.findProduct(options.id)
        return new Product(yourProduct)
    },
    async list(ctx, options) {
        const yourProducts = await ctx.db.listProducts({
            limit: options.limit,
            offset: options.cursor // the value you pass as `next` below
        })
        return {
            data: yourProducts.map(product => new Product(product)),
            // pass the next cursor if there are more items
            next: yourProducts.length === options.limit ? offset + limit : undefined
        }
    }
})
```
::



## Endpoints

###  Retrieve :badge{label="Required" color="green"}

`GET /churnkey/products/:id`

This endpoint fetches `Product` by its `id`. Usually, implementation will include finding a product in your database and mapping it to the [Product model](/integrations/models/product).

::collapsible{name="response"}
    ::tabs
        ::div{label="200"}
        Must return `Product` model. See [Product model documentation](/integrations/models/product).
        ::
        ::div{label="Error"}
        See [Error Responses](/integrations/general#error-responses).
        ::
    ::
::

::collapsible{name="code example"}
    ::code-group
    ```typescript [Typescript Express]
    import { Product } from '../models/Product'

    app.get('/churnkey/products/:id', async (req, res) => {
        const product = await db.findProductById(req.params.id)
        if (!product) {
            return res.status(404).send({ code: 404, message: 'Product not found' })
        }
        res.send(new Product(product))
    })
    ```
    ::
::

### List :badge{label="Required" color="green"}
`GET /churnkey/products`

This endpoint fetches a list of products from your database. You should find products in your database (with pagination), map them to the `Product` model and return a paginated list.

[Learn more about pagination](/integrations/general#pagination).

::collapsible{name="query parameters"}
  :field-schema{schema="/types/pagination-query.type"}
::
::collapsible{name="response"}
  :field-schema{schema="/types/pagination-response.type"}
::

::collapsible{name="code example"}
    ::code-group
    ```typescript [Typescript Express]
    import { Product } from '../models/Product'

    app.get('/churnkey/products', async (req, res) => {
        const limit = Number.parseInt(req.query.limit)
        const offset = Number.parseInt(req.query.cursor) 
        const products = await db.findProducts({ limit, offset })
        res.send({
            data: products.map(c => new Product(c)),
            next: products.length === limit ? offset + limit : undefined
        })
    })
    ```
    ::
::


## Webhooks
Coming soon.
