Build an HTTP service

Contents

This guide will provide you with the basic knowledge about writing HTTP services in Enonic XP.

Additional information on HTTP services can be found in the offical XP documentation.

What is an HTTP service

HTTP services are HTTP controllers that get exposed on urls matching a specific pattern.

In order to create a service, you’ll need to create a Javascript controller and place it under src/main/resources/services/<service-name>/<service-name>.js

JS controllers should return a specific response object.

Url pattern and context

This is the url pattern in which the service controller will get exposed: **/_/service/<appname>/<servicename>

That means that the same service can be invoked in multiple locations:

  1. domain.com/_/service/<appname>/<servicename>

  2. domain.com/some/path/_/service/<appname>/<servicename>

The url you choose to call your service on will define the context of the call. For example, depending on the chosen url, you’ll get your service running under different projects and/or branches:

  1. …​/site/my-first-project/draft/_/service/<appname>/<servicename>

  2. …​/site/my-first-project/master/_/service/<appname>/<servicename>

  3. …​/site/my-second-project/draft/_/service/<appname>/<servicename>

In the examples above, the first will run on my-first-project in the draft branch. The second will run on the same project, but in the master branch. Finally, the last one will run the my-second-project in the draft branch.

lib context can be used inside the service controller to ensure a specific context of the call.

Access restriction

It is also possible to restrict access to a service based on user roles.

This can be done without custom coding, you just need to add a <service-name>.xml file in your service folder with the following structure:

<!-- src/main/resources/services/<service-name>.xml -->
<service>
   <allow>
       <principal>role:system.admin</principal>
       <principal>role:myapp.myrole</principal>
   </allow>
</service>

Generating a valid url

Instead of manually writing a valid service url to call it in your application, Enonic’s Portal JS API provides serviceUrl method to generate a valid url pointing to a service.

The serviceUrl` function will mount a url to a service based on the context of where it was called.

Example of a service

Now that we know what services are, let’s build our own.

Our goal is to create a service that returns a JSON with data from a specific content type based on parameters provided by a service url.

  1. Start by creating a very simple Person content type:

    <content-type>
     <display-name>Person</display-name>
     <description>
        A minimal Person content type
     </description>
     <super-type>base:structured</super-type>
     <form>
       <input type="TextLine" name="firstName">
           <label>First name</label>
           <occurrences minimum="1" maximum="1"/>
       </input>
       <input type="TextLine" name="lastName">
           <label>Last name</label>
           <occurrences minimum="1" maximum="1"/>
       </input>
     </form>
    </content-type>

    For sake of simplicity, our service will expect a GET HTTP request with some specific (but not required) query parameters.

  2. Since we’d like to fetch the person content type, it makes sense to call our service something like people or getPeople. Let’s stick with people, therefore create a JS file called src/main/resources/services/people/people.js with the following code:

    exports.get = function (request) {};
  3. Let’s now define our query parameters:

    1. s will be a string parameter to look for person’s firstName or lastName (default: empty string)

    2. l will be a parameter to limit the number of results (default: 5)

  4. Now that we’ve defined parameters and values, let’s investigate what is returned on the request parameter of the GET handler function. To do that, update the service code to:

    exports.get = function (request) {
       log.info(JSON.stringify(request, null, 4));
    };

    Then build and deploy your application.

    To call your people service, access the following url changing <appname> to the name of your app: http://localhost:8080/_/service/<appname>/people?s=my-search-term&l=10

    The name of your app can be located at gradle.properties file. It’ll be the appName config.
  5. After visiting the url in your preferred browser, check the server logs. You’ll notice that query paramters are on request.params

    A full description of the request object can be found here.
  6. Finally, let’s properly code the service:

    const contentLib = require('/lib/xp/content');
    
    exports.get = function (request) {
        const searchTerm = request.params.s || '';
        const limit = request.params.l || 5;
    
        let status, data, body, error;
    
        try {
            const people = contentLib.query({
                count: limit,
                sort: "data.firstName ASC",
                query: `data.firstName LIKE '*${searchTerm}*' OR data.lastName LIKE '*${searchTerm}*'`,
                contentTypes: [`${app.name}:person`]
            });
            
            status = 200;
            data = people.hits;
        } catch(err) {
            status = 500;
            error = err.toString();
        }
    
        if (status === 200) {
            body = JSON.stringify({ data });
        }
    
        if (status === 500) {
            body = JSON.stringify({ error });
        }
    
        return { status: status, body: body, contentType: 'application/json' };
    };
  7. Now make sure to create some instances of the person content type. Build and deploy the application.

  8. Finally, access the service in the proper context, i.e, using the proper project name, branch and app name: http://localhost:8080/site/<project-name>/<branch>/_/service/<appname>/people?s=pers

    Service example

    The project name can be found in the top left corner of Content Studio. If you haven’t set up a new project, it is probably the default one. Branch will be draft or master. App name was already described before, and is located in gradle.properties
Depending on the context it is possible to get no data in response. The reason might be that in that context you don’t have person content type instances to be queried by content lib.

Other common use cases

In the previous example we made a service that provided content type data based on query parameters.

This was just a simple example of how services can be used. Here are some other common use cases for HTTP services:

  • Provide data for a custom selector

  • Process POST requests from forms in an application

  • Directly manage content inside repos

  • Much more creative scenarios when used together with some Enonic JS libraries


Contents

Contents