GraphQL
GraphQL是一种API语言。用一句话描述它:只请求需要的,并确切获得所需的。
语法学习:官方文档
Overview
API调用最常见的情况:查询和修改。在RESTful风格的API中我们常用get方法查询数据,用post、put、delete方法修改数据。而在GraphQL里,对应的方法是 query 和 mutation。
使用GraphQL API的好处:前端可以在一个请求中执行多个查询。它使用 types 确保应用仅获取可能数据,并提供清晰有用的错误,同时避免编写手动解析代码。
Shopify同时支持RESTful API和GraphQL API。在为Shopify开发app时,官方鼓励使用GraphQL API。此外,通过独立站店铺内安装的GraphQL app,前端可以方便地排查线上数据问题。
简单的使用技巧
- 虽然不是必须,但建议给 query 和 mutation 命名,类似匿名函数和命名函数的区别,便于在批量请求失败时,debug和定位问题
- 命名遵守规则:描述要返回的内容
运行示例代码
你需要注册Shopify开发者账号,开通development store,导入一些商品数据,安装 Shopify GraphiQL App https://shopify-graphiql-app.shopifycloud.com/。这些都是免费的。
以Shopify API为例
示例: 获取店铺前2个商品的id和标题
query FirstTwoProducts {
products(first: 2) {
edges {
node {
id
title
}
}
}
}
其中 first
是 products 的argument参数。edges
是 Shopify 实现的 connection,这里是商品list。
响应如下:
{
"data": {
"products": {
"edges": [
{
"node": {
"id": "gid://shopify/Product/6429646454933",
"title": "The Round Neck Sweater Female Cashmere Loose No Cap Hedging Student Wild"
}
},
{
"node": {
"id": "gid://shopify/Product/6429658120341",
"title": "Long Sleeves Hooded Solid Sweater Fleece Loose Version"
}
}
]
}
},
"extensions": {
"cost": {
"requestedQueryCost": 4,
"actualQueryCost": 4,
"throttleStatus": {
"maximumAvailable": 1000,
"currentlyAvailable": 996,
"restoreRate": 50
}
}
}
}
extensions 的内容是Shopify GraphQL API用来反馈当前api调用的开销情况。Shopify限制了API调用的频率,感兴趣的可以搜一下。后面的响应代码省略extensions信息。
示例0:过滤查询结果
Shopify 提供了 query
这个filter,可以对的结果集connection近一步过滤。
以下代码执行 Prefix query,获取店铺前10个 collections 中,标题包含’Best’字符串的collection。
注意:执行Field search时,field和value之间不能有空格。否则 title: Best*
,查询的是 title
和 Best*
。
query CollectionHandler {
collections(first: 10, query: "title:Best*") {
edges {
node {
title
handle
}
}
}
}
响应如下:
{
"data": {
"collections": {
"edges": [
{
"node": {
"title": "Women's Bags :heart: Best Seller",
"handle": "womens-bags-best-seller"
}
},
{
"node": {
"title": "Best Seller- Home",
"handle": "best-seller-home"
}
},
{
"node": {
"title": "Best Sellers :boom::fire:",
"handle": "best-sellers-:boom::fire:"
}
},
{
"node": {
"title": "Best Seller",
"handle": "best-seller"
}
}
]
}
},
"extensions": ...
}
其中 search 语法看这里:https://shopify.dev/concepts/about-apis/search-syntax 可以查询的resources,定义在文档的references里。
示例1:pagination分页
在上面的示例中,还可以获取商品variants变体(也是一个edges list),同理,可以在 edges 同级加上 pageInfo,可以知道是否有前一页/后一页。
我们可以在每一层的list中,使用分页,实现nest pagination嵌套分页的效果。
query FirstTwoProducts {
products(first: 2) {
edges {
node {
id
title
variants(first: 1) {
edges {
node {
id
title
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
}
}
}
响应如下:
{
"data": {
"products": {
"edges": [
{
"node": {
"id": "gid://shopify/Product/6429646454933",
"title": "The Round Neck Sweater Female Cashmere Loose No Cap Hedging Student Wild",
"variants": {
"edges": [
{
"node": {
"id": "gid://shopify/ProductVariant/38008678973589",
"title": "S / Yellow"
}
}
],
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false
}
}
}
},
{
"node": {
"id": "gid://shopify/Product/6429658120341",
"title": "Long Sleeves Hooded Solid Sweater Fleece Loose Version",
"variants": {
"edges": [
{
"node": {
"id": "gid://shopify/ProductVariant/38008701681813",
"title": "L / Brown"
}
}
],
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false
}
}
}
}
]
}
},
"extensions": ...
}
示例2: 命名查询、变量
根据id获取指定商品的标题和描述
query ProductTitleAndDescription($id: ID!) {
product(id: $id) {
title
description
}
}
查询参数
{
"id": "gid://shopify/Product/6429646454933"
}
响应如下:
{
"data": {
"product": {
"title": "The Round Neck Sweater Female Cashmere Loose No Cap Hedging Student Wild",
"description": "Thickness:Thicken Fitness:Loose Clothing Length:LongBelt:NoStyle:SweetPattern:PatternPopular Elements:Printing Sleeve Length:Long Sleeve Material:Polyester Seller:Credibility Solutions Private Limited"
}
},
"extensions": ...
}
示例3:别名
即重命名返回字段。默认地,返回有效数据部分的json结构与查询graphQL语句是一样的。
在示例2代码上稍作修改:
query ProductTitleAndDescription($id: ID!) {
product(id: $id) {
myTitle: title
description
}
}
可以看到,仍然返回 title 的值,但是字段名变为“myTitle”。
示例4 别名 - 批量操作
别名真正的优势在于:在一个请求中,多次查询同一个字段。
下面的代码通过别名,使用不同的arguments,在一个请求里使用两次product fields
query {
product1: product(id: "gid://shopify/Product/6429646454933") {
title
description
}
product2: product(id: "gid://shopify/Product/6429658120341") {
title
description
}
}
响应如下:
{
"data": {
"product1": {
"title": "The Round Neck Sweater Female Cashmere Loose No Cap Hedging Student Wild",
"description": "Thickness:Thicken Fitness:Loose Clothing Length:LongBelt:NoStyle:SweetPattern:PatternPopular Elements:Printing Sleeve Length:Long Sleeve Material:Polyester Seller:Credibility Solutions Private Limited"
},
"product2": {
"title": "Long Sleeves Hooded Solid Sweater Fleece Loose Version",
"description": "Material:Cotton Fitness:Regular Thickness:Regular Seller:Credibility Solutions Private Limited"
}
},
"extensions": ...
}
示例5:fragment片段
参考示例4,在批量操作时,定义返回字段的代码稍显重复,用 fragment 定义返回字段,就可以只维护一份代码.
query {
product1: product(id: "gid://shopify/Product/6429646454933") {
...TitleAndDescription
}
product2: product(id: "gid://shopify/Product/6429658120341") {
...TitleAndDescription
}
}
fragment TitleAndDescription on Product {
title
description
}
返回结果同示例4。
示例6:Inline Fragments内联片段
许多不同类型 implement实现了相同的 interface接口。
如:Shopify中的common object-》Node
下面是Shopify api中的添加tag的接口,默认返回 node 对象的 id。
因为Product类型实现了Node,通过在返回字段定义内联fragment,就可以额外获取到这个商品的信息(如:标题)。
mutation tagsAdd($id: ID!, $tags: [String!]!) {
tagsAdd(id: $id, tags: $tags) {
node {
id
...on Product {
title
}
...on Customer { // 因为 tagsAdd 适用于任何 Node,这样写可以基于返回内容(id的类型),获得不同字段
email
addresses
}
}
userErrors {
field
message
}
}
}
参数:
{
"id": "gid://shopify/Product/6429646454933",
"tags": [
"on-sale"
]
}
示例7:根据ID查询在线Page的法语翻译
店铺默认语言为意大利语,Page原内容是英文。
query TranslationFR ($id: ID!) {
translatableResource(resourceId: $id) {
translations(locale: "fr") {
key
value
}
translatableContent {
key
value
locale
}
}
}
查询参数
{
"id": "gid://shopify/OnlineStorePage/80469131458"
}
响应如下:
{
"data": {
"translatableResource": {
"translations": [
{
"key": "body_html",
"value": ...
},
{
"key": "meta_description",
"value": ...
},
{
"key": "meta_title",
"value": "Contactez-nous"
},
{
"key": "title",
"value": "Contactez-nous"
}
],
"translatableContent": [
{
"key": "title",
"value": "Contact Us",
"locale": "it"
},
{
"key": "body_html",
"value": ...,
"locale": "it"
},
{
"key": "meta_title",
"value": null,
"locale": "it"
},
{
"key": "meta_description",
"value": null,
"locale": "it"
}
]
}
},
"extensions": ...
}
某些字段的响应value省略了,示例中的页面比较特殊,除了title,主体内容都是图片。可以看到,meta_title、meta_description由于没有意大利语翻译,value是null。
示例8:根据ID删除商品某个metafields
调研Shopify Product Reviews功能时用csv给某商品倒入了一些评分评论数据,这些数据通过扩展 product.metafields 保存。
先查询得到该 metafield 的唯一标识(id):
query ProductMetafields($id: ID!) {
product(id: $id) {
title
handle
metafields(first:20) {
edges {
node {
id
key
value
namespace
}
}
}
}
}
响应:
{
"data": {
"product": {
...
"metafields": {
"edges": [
...,
{
"node": {
"key": "reviews",
"value": "<style scoped>.spr-container {\n padding: 24px;\n border-color: #ECECEC;}\n .spr-review, .spr-form {\n border-color: #ECECEC;\n }\n</style>\n\n<div class=\"spr-container\">\n <div class=\"spr-header\">\n <h2 class=\"spr-header-title\">Customer Reviews</h2><div class=\"spr-summary\">\n\n <span class=\"spr-starrating spr-summary-starrating\">\n <i class=\"spr-icon spr-icon-star\"></i><i class=\"spr-icon spr-icon-star\"></i><i class=\"spr-icon spr-icon-star\"></i><i class=\"spr-icon spr-icon-star\"></i><i class=\"spr-icon spr-icon-star\"></i>\n </span>\n <span class=\"spr-summary-caption\"><span class='spr-summary-actions-togglereviews'>Based on 6 reviews</span>\n </span><span class=\"spr-summary-actions\">\n <a href='#' class='spr-summary-actions-newreview' onclick='SPR.toggleForm(6824438104223);return false'>Write a review</a>\n </span>\n </div>\n </div>\n\n <div class=\"spr-content\">\n <div class='spr-form' id='form_6824438104223' style='display: none'></div>\n <div class='spr-reviews' id='reviews_6824438104223' ></div>\n </div>\n\n</div>\n<script type=\"application/ld+json\">\n {\n \"@context\": \"http://schema.org/\",\n \"@type\": \"AggregateRating\",\n \"reviewCount\": \"6\",\n \"ratingValue\": \"4.833333333333333\",\n \"itemReviewed\": {\n \"@type\" : \"Product\",\n \"name\" : \"2021 Waterproof Sun UV Protection Man Quick Dry Hiking Jacket Coats Outdoor Windbreaker Sports Fishing Skin Jackets\",\n \"offers\": {\n \"@type\": \"AggregateOffer\",\n \"lowPrice\": \"5.99\",\n \"highPrice\": \"5.99\",\n \"priceCurrency\": \"GBP\"\n }\n }\n }\n</script>",
"namespace": "spr",
"createdAt": "2021-07-30T06:21:52Z"
}
}
]
}
}
},
"extensions": ...
}
再参考官方文档指令进行删除:
入参
{
"input": {
"id": "gid://shopify/Metafield/19993346244767"
}
}
命令
mutation metafieldDelete($input: MetafieldDeleteInput!) {
metafieldDelete(input: $input) {
deletedId
userErrors {
field
message
}
}
}
示例9:根据商品id添加商品标题的翻译
找到商品 title 字段 translatableContent 的 digest
入参:
{
"id": "gid://shopify/Product/6830238204096"
}
命令:
query ProductTranslatableContentById($id: ID!) {
translatableResource(resourceId: $id) {
resourceId
translatableContent {
key
value
digest
locale
}
}
}
返回:
{
"data": {
"translatableResource": {
"resourceId": "gid://shopify/Product/6830238204096",
"translatableContent": [
{
"key": "title",
"value": "Female Loose Yoga Clothes",
"digest": "df1b7e178043e843b0410af2bec05529f11e7afd1389890c305f7a537e4ec2ba",
"locale": "en"
},
{
"key": "body_html",
"value": "<div id=\"titleHeader\">Female Loose Yoga Clothes</div><br>",
"digest": "5c51f5a217d35e566c2e992c5309debab8954b146084c451ed47a10c2e8b8b4e",
"locale": "en"
},
...
}
}
},
...
}
使用 Shopify spuId、digest、词条(另 key 和 locale 是固定值)在 Shopify 数据库里添加翻译。
入参:
{
"id": "gid://shopify/Product/6830238204096",
"translations": [
{
"key": "title",
"value": "weiblich lose Yoga Kleider",
"locale": "de",
"translatableContentDigest": "df1b7e178043e843b0410af2bec05529f11e7afd1389890c305f7a537e4ec2ba"
}
]
}
命令:
mutation CreateTranslation($id: ID!, $translations: [TranslationInput!]!) {
translationsRegister(resourceId: $id, translations: $translations) {
userErrors {
message
field
}
translations {
locale
key
value
}
}
}
示例10:根据商品id查看指定语言的翻译
命令:
query ProductTranslationsById($id: ID!){
product(id: $id) {
title
translations(locale: "de") {
locale
key
value
}
}
}
示例11:删除指定语言的某个翻译
入参:
{
"id": "gid://shopify/Product/6830238204096",
"keys": [
"title"
],
"locales": [
"de"
]
}
命令:
mutation RemoveTranslations($id: ID!, $keys: [String!]!, $locales: [String!]!) {
translationsRemove(
resourceId: $id,
translationKeys: $keys,
locales: $locales
) {
userErrors {
message
field
}
translations {
locale
key
value
}
}
}