Back to Examples

GraphQL service - Context object

The Ballerina graphql module allows defining and using a graphql:Context object. The contextInit field in the graphql:ServiceConfig annotation can be used to pass the context initialization function. If it is not provided, a default, empty context object will be created per request. When the graphql:Context is needed to be accessed, define it as a parameter of the resource/remote method. Use the graphql:Context to pass meta information between the resource/remote methods used as GraphQL object fields.

Hint: The graphql:Context is defined before the other parameters of a function as a convention.

Note: If the graphql:Context is defined as a parameter of a resolver function, it will be accessible inside the resolver. Passing it down is not necessary.

import ballerina/graphql;
import ballerina/http;

@graphql:ServiceConfig {
    // Initialization of the `graphqlContext` should be provided to the `contextInit` field.
    contextInit
}
service /graphql on new graphql:Listener(9090) {
    // Defines a `Profile` field inside the service.
    private final Profile profile;

    function init() {
        // Initializes the `profile` value.
        self.profile = new ("Walter White", 51, 737000.00);
    }

    // If the context is needed, it should be defined as a parameter of the resolver function.
    resource function get profile(graphql:Context context) returns Profile|error {
        // The profile information will be returned only if the scope is `admin` or `user`.
        check validateScope(context, ["admin", "user"]);
        return self.profile;
    }
}

// Defines a service class to use as an object in the GraphQL service.
service class Profile {
    private final string name;
    private final int age;
    private final float salary;

    function init(string name, int age, float salary) {
        self.name = name;
        self.age = age;
        self.salary = salary;
    }

    resource function get name() returns string => self.name;

    resource function get age() returns int => self.age;

    // If the context is needed, it should just be specified as a parameter of the resolver method.
    // Ballerina handles propagating the context, and therefore, it is not required to be passed 
    // as an argument to the `init` method from the parent resolver.
    resource function get salary(graphql:Context context) returns float|error {
        // The salary information will be returned only if the scope is `admin`.
        check validateScope(context, ["admin"]);
        return self.salary;
    }
}

isolated function validateScope(graphql:Context context, string[] allowedScopes) returns error? {
    // Retrieves the `scope` attribute from the context. This will return a `graphql:Error` if
    // the `scope` is not found in the context.
    final string scope = check context.get("scope").ensureType();
    // If the scope doesn't matches any of the allowed scopes return an `error`.
    if !allowedScopes.some(allowedScope => scope == allowedScope) {
        // Returns an `error` if the required scope is not found.
        return error("Permission denied");
    }
}

isolated function contextInit(http:RequestContext requestContext, http:Request request) returns graphql:Context|error {
    // Initialize the `graphql:Context` object.
    graphql:Context context = new;

    // Retrieves the header named `scope` from the `http:request` and set it to the context with
    // the `scope` key. If the header does not exist, this will return an `error`, and thereby,
    // the request will not be processed.
    context.set("scope", check request.getHeader("scope"));

    // Finally, the context object should be returned.
    return context;
}

Run the service by executing the following command.

$ bal run graphql_context.bal

Send the following document to the GraphQL endpoint to test the service.

{
    profile {
        name
        salary
    }
}

To send the document, execute the following cURL command in a separate terminal. First, send the request with the scope header value set to admin.

$ curl -X POST -H "Content-type: application/json" -H "scope: admin" -d '{ "query": "{ profile { name salary } }" }' 'http://localhost:9090/graphql'{"data":{"profile":{"name":"Walter White", "salary":737000.0}}}

Now, send the same document with the scope header value set to unknown. This will return an error in the profile field.

$ curl -X POST -H "Content-type: application/json" -H "scope: unknown" -d '{ "query": "{ profile { name salary } }" }' 'http://localhost:9090/graphql'{"errors":[{"message":"Permission denied", "locations":[{"line":1, "column":3}], "path":["profile"]}], "data":null}

Tip: You can invoke the above service via the GraphQL client.

Related links

PreviousHierarchical resource paths
NextField object