# Part 2: GraphQL API

At this point, the admin can upload and manage 3D models for each product, but these models are not yet visible to the frontend. However, before we can start implementing the frontend logic for product 3D models, we need to expose this data through an API.

{% hint style="info" %}
GraphQL is an API language has [several advantages](https://docs.scandipwa.com/about/why-scandipwa/challenges#api-complexity) over the conventional REST APIs – greater flexibility, better API documentation, type checking, and a simple query structure.
{% endhint %}

[Scandi uses GraphQL](https://docs.scandipwa.com/magento/developing-the-magento-backend) to fetch data from the backend. For example, to fetch product data, Scandi uses the `products` query:

{% code title="Example GraphQL query:" %}

```graphql
{
  products(search:"bag") {
    items {
      id
      name
    }
  }
}

```

{% endcode %}

{% code title="Corresponding response:" %}

```javascript
{
  "data": {
    "products": {
      "items": [
        {
          "id": 1,
          "name": "Joust Duffle Bag"
        },
        {
          "id": 8,
          "name": "Voyage Yoga Bag"
        },
        {
          "id": 4,
          "name": "Wayfarer Messenger Bag"
        }
      ]
    }
  }
}
```

{% endcode %}

This query already has multiple useful fields – we can query the product name, sku, price and other properties if we want. But now we want to add a brand-new queryable field: `model_3d_urls`. This field will return an array of strings, each representing the URL of a 3D model associated with the product.

{% hint style="info" %}
You can use a GraphQL client such as [Altair GraphQL Client](https://altair.sirmuel.design/) to make GraphQL queries easily. This can be useful when exploring an existing API as we did above, or to debug your own API, as we are about to do.

Make sure you set the API URL to <http://localhost/graphql> (assuming your M2 server is running at `localhost`) and you're good to go – you can try out the example query from above!
{% endhint %}

## Extending the GraphQL Schema

A GraphQL Schema is a document that describes the fields available in a GraphQL API. For example, there is a schema that specifies that the `items` field of the `products` query is an array of `ProductInterface`. There is another schema file that describes the fields that are part of `ProductInterface`:

{% code title="vendor/magento/module-catalog-graph-ql/etc/schema.graphqls (snippet)" %}

```graphql
interface ProductInterface
{
    id: Int
    name: String
    sku: String @doc(description: "A code assigned to a product")
    description: ComplexTextValue
    # [...]
}    
```

{% endcode %}

{% hint style="info" %}
Each field consists of a name for the field (like "id"), and a corresponding type (Int/String, etc.). This describes the values that the API is expected to have.
{% endhint %}

Because of the way GraphQL is setup in Magento, all of the schema files are merged together. This means that we can easily "extend" the `ProductInterface` type to include other fields as well. All we need to do is create a new GraphQL schema file in our module (in `etc/schema.graphqls`), and declare the additional `ProductInterface` fields there:

{% code title="etc/schema.graphqls" %}

```graphql
interface ProductInterface {
    model_3d_urls: [String!]!
}
```

{% endcode %}

And we have updated the GraphQL schema! Run `magento setup:upgrade` for Magento to update it.

{% hint style="info" %}
The exclamation mark (`!`) means that the field cannot be null. This means that values such as `model_3d_urls: null` and `model_3d_urls: [null, null]` are not valid. It is a good idea to tell GraphQL that we expect these values to be present on any product if that is the case, because this will result in an error if the field is not set, helping catch bugs early.
{% endhint %}

## Implementing the Field

Now, our `ProductInterface` type provides an additional field. We have made a "promise" to API users that they can query a `model_3d_urls` field on any product. Indeed, if we refresh the schema in our GraphQL client, we are now able to make a query such as this one:

```graphql
{
  products(search:"bag") {
    items {
      id
      name
      model_3d_urls
    }
  }
}
```

However, if we send the query, we will get an error – because there is nothing in the current resolver that would populate `model_3d_urls` with a value, and GraphQL is complaining that the data returned by the resolver does not match the schema we defined.

{% hint style="info" %}
A GraphQL Resolver is an implementation of the schema. It is the PHP code that actually handles PHP requests and returns a data object of the response.
{% endhint %}

The `products` query already has a resolver – that's the bit of code that returns the `items` so that we can see the `id`, `name` and other fields. Now, we want to "plug in" to the resolver so that it returns the additional data we want.

The best way to "plug in" depends on how the resolver is implemented. Sometimes, we have to use Magento plugins (interceptors) to "wrap around" the resolver and add additional data before it returns. In this case, however, there is a specific class whose purpose is to process the product data after it is generated, but before it is returned through GraphQL.

The product `DataPostProcessor` accepts an array of objects that implement the `ProductsDataPostProcessorInterface`. Then, whenever the product collection is processed, it passes the data through each of the processor objects, allowing them to modify the data.

We can use the dependency injection configuration file to inject an additional processor:

{% code title="etc/di.xmletc/di.xml" %}

```markup
<?xml version="1.0"?>
<config
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"
>
    <!-- [...] -->

    <type name="ScandiPWA\Performance\Model\Resolver\Products\DataPostProcessor">
        <arguments>
            <argument name="processors" xsi:type="array">
                <item name="model_3d_urls" xsi:type="object">
Scandi\Product3DGraphQl\Model\Resolver\Products\CollectionPostProcessor\Model3DProcessor
                </item>
            </argument>
        </arguments>
    </type>
</config>
```

{% endcode %}

Then, we can define that class and implement the functionality we want:

```php
<?php declare(strict_types=1);
namespace Scandi\Product3DGraphQl\Model\Resolver\Products\CollectionPostProcessor;

use Scandi\Product3DGraphQl\Helper\Model3DProvider;
use ScandiPWA\Performance\Api\ProductsDataPostProcessorInterface;

class Model3DProcessor implements ProductsDataPostProcessorInterface
{
    const MODEL_FIELD = 'model_3d_urls';

    /** @var Model3DProvider */
    protected $modelProvider;
    
    // [DI constructor omitted]

    public function process(
        array $products,
        string $graphqlResolvePath,
        $graphqlResolveInfo,
        ?array $processorOptions = []
    ): callable {
        // Collect relevant product IDs:
        $ids = [];
        foreach ($products as $product) {
            $ids[] = $product->getId();
        }

        // Get all 3D Models that belong to those IDs (1 SQL request)
        $modelsByProductId = $this->modelProvider->getModelsForProductIds($ids);

        // Return a function that adds a new field to the given product
        return function (&$product) use ($modelsByProductId) {
            $models = $modelsByProductId[$product['entity_id']];

            $urls = [];
            foreach ($models as $model) {
                $urls[] = $model->getResourceUrl();
            }

            $product[self::MODEL_FIELD] = $urls;
        };
    }
}
```

You can now verify that we can query the 3D Model URLs of the products and they will be returned correctly. Example response:

```javascript
{
  "data": {
    "products": {
      "items": [
        {
          "id": 1,
          "name": "Joust Duffle Bag",
          "model_3d_urls": [
            "http://localhost/media/scandi/product_3d_models/m/o/model1.glb",
            "http://localhost/media/scandi/product_3d_models/m/o/model2.glb"
          ]
        },
        {
          "id": 8,
          "name": "Voyage Yoga Bag",
          "model_3d_urls": []
        },
        {
          "id": 4,
          "name": "Wayfarer Messenger Bag",
          "model_3d_urls": []
        },
        {
          "id": 14,
          "name": "Push It Messenger Bag",
          "model_3d_urls": []
        }
      ]
    }
  }
}
```

{% hint style="success" %}
This works as expected – products with no 3D models get an empty array in `model_3d_urls` and products with 3D models have an array of valid model URLs.
{% endhint %}
