GraphQL: Så bygger du flexibla API:er med queries och mutations
GraphQL låter klienten bestämma exakt vilken data den behöver. Lär dig schema-design, resolvers och hur du undviker vanliga fallgropar.
Varför GraphQL?
GraphQL löser ett grundläggande problem med REST: over-fetching och under-fetching av data. Med REST-endpoints får du ofta antingen för mycket data (fält du inte behöver) eller för lite (kräver extra requests). GraphQL låter klienten specificera exakt vilka fält den vill ha i en enda request.
För team som bygger produkter med komplexa datarelationer (t.ex. en e-handelsplattform där produkter har kategorier, recensioner och lagerstatus) ger GraphQL markanta fördelar i utvecklingshastighet och nätverksprestanda.
# REST: Kräver 3 separata requests
GET /api/user/123
GET /api/user/123/orders
GET /api/user/123/orders/456/items
# GraphQL: En enda query
query {
user(id: "123") {
name
email
orders(last: 5) {
id
total
items {
product { name, price }
quantity
}
}
}
}Schema-design
GraphQL-schemat är kontraktet mellan klient och server. Det definierar vilka typer som finns, vilka queries som kan ställas och vilka mutations som kan utföras. Ett genomtänkt schema är nyckeln till ett bra GraphQL-API.
Använd beskrivande typnamn, undvik djupa nästningar och designa med framtida behov i åtanke utan att överkomplicera. Interface och unions hjälper dig att modellera polymorfisk data.
# Schema-definition med SDL
type Product {
id: ID!
name: String!
description: String
price: Float!
category: Category!
reviews: [Review!]!
inStock: Boolean!
createdAt: DateTime!
}
type Category {
id: ID!
name: String!
products(first: Int, after: String): ProductConnection!
}
type Review {
id: ID!
rating: Int!
comment: String
author: User!
}
type Query {
product(id: ID!): Product
products(
filter: ProductFilter
first: Int
after: String
): ProductConnection!
categories: [Category!]!
}
type Mutation {
createProduct(input: CreateProductInput!): Product!
updateProduct(id: ID!, input: UpdateProductInput!): Product!
deleteProduct(id: ID!): Boolean!
}
input CreateProductInput {
name: String!
description: String
price: Float!
categoryId: ID!
}Resolvers i praktiken
Resolvers är funktionerna som hämtar data för varje fält i schemat. En resolver tar emot parent-objektet, argument, kontext (med autentisering och datakällor) och info om queryn. Effektiva resolvers använder DataLoader för att undvika N+1-problemet, där en lista med 100 produkter annars skulle trigga 100 separata databasanrop för att hämta tillhörande kategorier.
// Apollo Server resolvers
import DataLoader from "dataloader";
const resolvers = {
Query: {
product: async (_, { id }, { db }) => {
return db.products.findUnique({ where: { id } });
},
products: async (_, { filter, first = 20, after }, { db }) => {
const products = await db.products.findMany({
where: filter ? { category: filter.category } : {},
take: first + 1,
cursor: after ? { id: after } : undefined,
});
return {
edges: products.slice(0, first).map((p) => ({
node: p,
cursor: p.id,
})),
pageInfo: {
hasNextPage: products.length > first,
endCursor: products[Math.min(first - 1, products.length - 1)]?.id,
},
};
},
},
Product: {
// DataLoader förhindrar N+1-problem
category: async (product, _, { categoryLoader }) => {
return categoryLoader.load(product.categoryId);
},
reviews: async (product, _, { db }) => {
return db.reviews.findMany({
where: { productId: product.id },
});
},
},
};Fler guider
Letar du efter pålitlig hosting för dina API-projekt?
Kolla in mehosting.com