The API Layer
Vendure is a headless platform, which means that all functionality is exposed via GraphQL APIs. The API can be thought of as a number of layers through which a request will pass, each of which is responsible for a different aspect of the request/response lifecycle.
The journey of an API call
Let's take a basic API call and trace its journey from the client to the server and back again.
This query is asking for the id, name and description of a Product with the id of 1.
If you have your local development server running, you can try this out by opening the GraphQL Playground in your browser:
Middleware
"Middleware" is a term for a function which is executed before or after the main logic of a request. In Vendure, middleware is used to perform tasks such as authentication, logging, and error handling. There are several types of middleware:
Express middleware
At the lowest level, Vendure makes use of the popular Express server library. Express middleware
can be added to the sever via the apiOptions.middleware config property. There are hundreds of tried-and-tested Express
middleware packages available, and they can be used to add functionality such as CORS, compression, rate-limiting, etc.
Here's a simple example demonstrating Express middleware which will log a message whenever a request is received to the Admin API:
NestJS middleware
You can also define NestJS middleware which works like Express middleware but also has access to the NestJS dependency injection system.
NestJS allows you to define specific types of middleware including Guards, Interceptors, Pipes and Filters.
Vendure uses a number of these mechanisms internally to handle authentication, transaction management, error handling and data transformation.
Global NestJS middleware
Guards, interceptors, pipes and filters can be added to your own custom resolvers and controllers using the NestJS decorators as given in the NestJS docs. However, a common pattern is to register them globally via a Vendure plugin:
Adding this plugin to your Vendure config plugins array will now apply these middleware classes to all requests.
Apollo Server plugins
Apollo Server (the underlying GraphQL server library used by Vendure) allows you to define
plugins which can be used to hook into various
stages of the GraphQL request lifecycle and perform tasks such as data transformation. These are defined via the
apiOptions.apolloServerPlugins config property.
Resolvers
A "resolver" is a GraphQL concept, and refers to a function which is responsible for returning the data for a particular field. In Vendure, a resolver can also refer to a class which contains multiple resolver functions. For every query or mutation, there is a corresponding resolver function which is responsible for returning the requested data (and performing side-effect such as updating data in the case of mutations).
Here's a simplified example of a resolver function for the product query:
- The
@Resolver()decorator marks this class as a resolver. - The
@Query()decorator marks theproduct()method as a resolver function. - The
@Ctx()decorator injects theRequestContextobject, which contains information about the current request, such as the current user, the active channel, the active language, etc. TheRequestContextis a key part of the Vendure architecture, and is used throughout the application to provide context to the various services and plugins. In general, your resolver functions should always accept aRequestContextas the first argument, and pass it through to the services. - The
@Args()decorator injects the arguments passed to the query, in this case theidthat we provided in our query.
As you can see, the resolver function is very simple, and simply delegates the work to the ProductService which is
responsible for fetching the data from the database.
In general, resolver functions should be kept as simple as possible, and the bulk of the business logic should be delegated to the service layer.
API Decorators
Following the pattern of NestJS, Vendure makes use of decorators to control various aspects of the API. Here are the important decorators to be aware of:
@Resolver()
This is exported by the @nestjs/graphql package. It marks a class as a resolver, meaning that its methods can be used
to resolve the fields of a GraphQL query or mutation.
@Query()
This is exported by the @nestjs/graphql package. It marks a method as a resolver function for a query. The method name
should match the name of the query in the GraphQL schema, or if the method name is different, a name can be provided as
an argument to the decorator.
@Mutation()
This is exported by the @nestjs/graphql package. It marks a method as a resolver function for a mutation. The method name
should match the name of the mutation in the GraphQL schema, or if the method name is different, a name can be provided as
an argument to the decorator.
@Allow()
The Allow decorator is exported by the @vendure/core package. It is used to control access to queries and mutations. It takes a list
of Permissions and if the current user does not have at least one of the
permissions, then the query or mutation will return an error.
@Transaction()
The Transaction decorator is exported by the @vendure/core package. It is used to wrap a resolver function in a database transaction. It is
normally used with mutations, since queries typically do not modify data.
The @Transaction() decorator only works when used with a RequestContext object (see the @Ctx() decorator below).
This is because the Transaction decorator stores the transaction context on the RequestContext object, and by passing
this object to the service layer, the services and thus database calls can access the transaction context.
@Ctx()
The Ctx decorator is exported by the @vendure/core package. It is used to inject the
RequestContext object into a resolver function. The RequestContext contains information about the
current request, such as the current user, the active channel, the active language, etc. The RequestContext is a key part
of the Vendure architecture, and is used throughout the application to provide context to the various services and plugins.
As a general rule, always use the @Ctx() decorator to inject the RequestContext into your resolver functions.
@Args()
This is exported by the @nestjs/graphql package. It is used to inject the arguments passed to a query or mutation.
Given a schema definition like this:
The resolver function would look like this:
As you can see, the @Args() decorator injects the arguments passed to the query, in this case the variantId that we provided in our query.
Field resolvers
So far, we've seen examples of resolvers for queries and mutations. However, there is another type of resolver which is used to resolve the fields of a type. For example, given the following schema definition:
The product field is a relation to the Product type. The product field resolver
would look like this:
Note that in this example, the @Resolver() decorator has an argument of 'WishlistItem'. This tells NestJS that
this resolver is for the WishlistItem type, and that when we use the @ResolveField() decorator, we are defining
a resolver for a field of that type.
In this example we're defining a resolver for the product field of the WishlistItem type. The
@ResolveField() decorator is used to mark a method as a field resolver. The method name should match the name of the
field in the GraphQL schema, or if the method name is different, a name can be provided as an argument to the decorator.
REST endpoints
Although Vendure is primarily a GraphQL-based API, it is possible to add REST endpoints to the API. This is useful if you need to integrate with a third-party service or client application which only supports REST, for example.
Creating a REST endpoint is covered in detail in the Add a REST endpoint guide.
