Using Gally API
Gally is a next-generation Searchandising Engine designed to easily create an API-first E-Commerce Search Engine for your composable commerce stack.
Since Gally is powered by API Platform, it offers REST and GraphQL endpoints for your application to insert and query data. Even though at its core Gally is focused on E-Commerce (e.g., it is aware of categories, products, or prices), you can still add and expose your custom entities if you want to. How that works is part of a separate blog post.
For the following code examples, I am using httpie to interact with the Gally REST API. The syntax should be easy to convert to curl or your favorite http CLI client.
First, we need to authenticate to get the required authentication token for all further requests:
http --verify no https://localhost/authentication_token email=gally@bitexpert.loc password=123456
Gally will respond with the auth token like this:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2ODQ2ODY1OTcsImV4cCI6MTY4NDcxNTM5Nywicm9sZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfQ09OVFJJQlVUT1IiXSwidXNlcm5hbWUiOiJpbmZvQGJpdGV4cGVydC5kZSJ9.cIlNOU0ShhAt-CZpN4nOARLwD2xfNk62QJNpbjvYoyvfI3eucCglHs4XGC8Rz0f694Io9kZwslmCiob8kkaHqfPpHHOtDUS6_LsY-6VgAzonT1vCvkn3Eod_3j4N-ouPfdSySSdwjrC7LzyZriqEEjNwonxl_9u9ACr_Q3u03VzOC1uzw3P21jv9EafzXunDw6LhBikUkgWAfqeKTqG3MKwhedC8QhNzLZM9WTcfRky2NEH2sAJT1exJkh8VhBAFoxTEHRnngM1FLjV1lQlXEJChNm4yYDcI74Xl1vUl6MwY0_glvTRLPJDK7eKu_7ozvIcoWfJJEJUS9coItvX-ZXlj9_nfCPDwws2W8qkgTXPM0LMxcMFaUSJGxEs9-IaRKc4pr9I_1ApS9NwK6tWUvDSOKGex3tjDf0Rm1ahn1My8at6yo4DEsB30UNq7BfMXVIB7txvG_tZmB_SP6VkHV2Co4coSHRglQ_8-YAWk1b24CC8Eh5WQLJYRuNUaV0igA3xHVh6OgySmgVT224uH0PxzwS-zJPmIr08eMSsGBCEVvSFaAJEG_OhT-dy9POwnbdC6nRAAJoY1D5Tq2ROhFLIRgkkGQ9YYHHxNWfnsqF92VTynebS1qTd64Gp2wgIzzhR3cdh8LIh0mkriVG0VetRvIa8njyLdkQADJk6hi-w"
}
Assuming we have imported the fixtures provided by Gally to have some test data in our database, let's find out what indexes (the datastore for our data) exist by running the following query:
http --verify no -A bearer -a "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2ODQ2ODY1OTcsImV4cCI6MTY4NDcxNTM5Nywicm9sZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfQ09OVFJJQlVUT1IiXSwidXNlcm5hbWUiOiJpbmZvQGJpdGV4cGVydC5kZSJ9.cIlNOU0ShhAt-CZpN4nOARLwD2xfNk62QJNpbjvYoyvfI3eucCglHs4XGC8Rz0f694Io9kZwslmCiob8kkaHqfPpHHOtDUS6_LsY-6VgAzonT1vCvkn3Eod_3j4N-ouPfdSySSdwjrC7LzyZriqEEjNwonxl_9u9ACr_Q3u03VzOC1uzw3P21jv9EafzXunDw6LhBikUkgWAfqeKTqG3MKwhedC8QhNzLZM9WTcfRky2NEH2sAJT1exJkh8VhBAFoxTEHRnngM1FLjV1lQlXEJChNm4yYDcI74Xl1vUl6MwY0_glvTRLPJDK7eKu_7ozvIcoWfJJEJUS9coItvX-ZXlj9_nfCPDwws2W8qkgTXPM0LMxcMFaUSJGxEs9-IaRKc4pr9I_1ApS9NwK6tWUvDSOKGex3tjDf0Rm1ahn1My8at6yo4DEsB30UNq7BfMXVIB7txvG_tZmB_SP6VkHV2Co4coSHRglQ_8-YAWk1b24CC8Eh5WQLJYRuNUaV0igA3xHVh6OgySmgVT224uH0PxzwS-zJPmIr08eMSsGBCEVvSFaAJEG_OhT-dy9POwnbdC6nRAAJoY1D5Tq2ROhFLIRgkkGQ9YYHHxNWfnsqF92VTynebS1qTd64Gp2wgIzzhR3cdh8LIh0mkriVG0VetRvIa8njyLdkQADJk6hi-w" GET https://localhost/indices
Gally will return a list of all indexes in the system. For convenience, I stripped out the ones that are not needed for this example:
{
"@context": "/contexts/Index",
"@id": "/indices",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "/indices/gally_en_en_product_20230521_122434",
"@type": "Index",
"aliases": [
".catalog_7",
".entity_product",
"gally_en_en_product"
],
"docsCount": 0,
"entityType": "product",
"localizedCatalog": "/localized_catalogs/7",
"name": "gally_en_en_product_20230521_122434",
"size": "226b",
"status": "live"
}
],
"hydra:totalItems": 12
}
To continue further, we need to remember the name of the index gally_en_en_product_20230521_122434
and the id of the localizedCatalog, which is 7
in the example above.
To store a product in Gally, the structure needs to match the attributes that have been created by the fixtures beforehand. A product JSON document looks like this:
{
"entity_id": "2345",
"attribute_set_id": "9",
"type_id": "simple",
"sku": "DEMO-SKU",
"has_options": false,
"required_options": false,
"visibility": {
"value": "4",
"label": "Catalog, Search"
},
"price": [{
"price": 19.9,
"original_price": 19.9,
"group_id": 0,
"is_discounted": false
}],
"cost": 8.88,
"indexed_attributes": [
"price",
"name",
"url_key",
"description",
"status"
],
"category": [{
"is_virtual": "false",
"category_uid": "Mg==",
"id": "cat_2"
},
{
"is_parent": true,
"is_virtual": "false",
"name": "Accessories",
"category_uid": "Mw==",
"id": "cat_3"
}
],
"name": [
"Demo Accessory"
],
"url_key": [
"demo-accessory"
],
"description": [
"<p>Some demo accessory</p>"
],
"status": [{
"value": 1,
"label": "Enabled"
}],
"stock": {
"qty": 100,
"status": true
}
}
To send the product data to Gally, we need to convert the JSON document into a string and send that, with the index name we extracted above, to Gally:
http --verify no -A bearer -a "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2ODQ2ODY1OTcsImV4cCI6MTY4NDcxNTM5Nywicm9sZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfQ09OVFJJQlVUT1IiXSwidXNlcm5hbWUiOiJpbmZvQGJpdGV4cGVydC5kZSJ9.cIlNOU0ShhAt-CZpN4nOARLwD2xfNk62QJNpbjvYoyvfI3eucCglHs4XGC8Rz0f694Io9kZwslmCiob8kkaHqfPpHHOtDUS6_LsY-6VgAzonT1vCvkn3Eod_3j4N-ouPfdSySSdwjrC7LzyZriqEEjNwonxl_9u9ACr_Q3u03VzOC1uzw3P21jv9EafzXunDw6LhBikUkgWAfqeKTqG3MKwhedC8QhNzLZM9WTcfRky2NEH2sAJT1exJkh8VhBAFoxTEHRnngM1FLjV1lQlXEJChNm4yYDcI74Xl1vUl6MwY0_glvTRLPJDK7eKu_7ozvIcoWfJJEJUS9coItvX-ZXlj9_nfCPDwws2W8qkgTXPM0LMxcMFaUSJGxEs9-IaRKc4pr9I_1ApS9NwK6tWUvDSOKGex3tjDf0Rm1ahn1My8at6yo4DEsB30UNq7BfMXVIB7txvG_tZmB_SP6VkHV2Co4coSHRglQ_8-YAWk1b24CC8Eh5WQLJYRuNUaV0igA3xHVh6OgySmgVT224uH0PxzwS-zJPmIr08eMSsGBCEVvSFaAJEG_OhT-dy9POwnbdC6nRAAJoY1D5Tq2ROhFLIRgkkGQ9YYHHxNWfnsqF92VTynebS1qTd64Gp2wgIzzhR3cdh8LIh0mkriVG0VetRvIa8njyLdkQADJk6hi-w" POST https://localhost/index_documents indexName=gally_en_en_product_20230521_122434 documents:='["{\"entity_id\": \"2345\",\"attribute_set_id\": \"9\",\"type_id\": \"simple\",\"sku\": \"DEMO-SKU\",\"has_options\": false,\"required_options\": false,\"visibility\": {\"value\": \"4\",\"label\": \"Catalog, Search\"},\"price\": [{\"price\": 19.9,\"original_price\": 19.9,\"group_id\": 0,\"is_discounted\": false}],\"cost\": 8.88,\"indexed_attributes\": [\"price\",\"name\",\"url_key\",\"description\",\"status\"],\"category\": [{\"is_virtual\": \"false\",\"category_uid\": \"Mg==\",\"id\": \"cat_2\"},{\"is_parent\": true,\"is_virtual\": \"false\",\"name\": \"Accessories\",\"category_uid\": \"Mw==\",\"id\": \"cat_3\"}],\"name\": [\"Demo Accessory\"],\"url_key\": [\"demo-accessory\"],\"description\": [\"<p>Some demo accessory</p>\"],\"status\": [{\"value\": 1,\"label\": \"Enabled\"}],\"stock\": {\"qty\": 100,\"status\": true}}"]'
You can post multiple documents at once via the documents array as long as you keep in mind that each document is a string, not a JSON representation.
To search for the products, you have to use a GraphQL query like this:
query getDocuments(
$entityType: String!,
$localizedCatalog: String!,
$currentPage: Int,
$pageSize: Int,
$search: String,
$sort: SortInput
) {
documents(
entityType: $entityType,
localizedCatalog: $localizedCatalog,
currentPage: $currentPage,
pageSize: $pageSize,
search: $search,
sort: $sort,
) {
collection {
...on Document { id data }
}
paginationInfo { lastPage itemsPerPage totalCount }
sortInfo {
current { field direction }
}
aggregations {
field
label
type
options { count label value }
hasMore
}
}
}
...and pass your search query via GraphQL query parameters. Since we want to search for products, we set the entityType
to product, and we pass 7 (see above) as the id for the localizedCatalog
, and search
contains the search string we want to search for:
{
"entityType": "product",
"localizedCatalog": "7",
"currentPage": 1,
"pageSize": 10,
"search": "DEMO"
}
As you can see from the example, interacting with Gally is quite straightforward, and for a standard E-Commerce use-case, you don't need many customizations. And since Gally abstracts Elasticsearch behind an API, there's not much Elasticsearch knowledge needed unless you need to fine-tune things for specific use cases.
If you want to know more about Gally or need help implementing it in your application, let us know. We're happy to help!