<!-- TITLE: How To Create A Backend Service --> <!-- SUBTITLE: A quick summary of How To Create A Backend Service --> # Create a backend service A backend service is a collection of Python files without a direct user interface (see [Yoeman-Generator](/phovea/fundamentals/phovea-yeoman-generator)). We will show this by **creating a REST service**. # Prerequisites This tutorial requires you to complete the following steps / tutorials: - [General prerequisites](/phovea/development/workspace/general-prerequisites) - [Create a workspase](/phovea/development/workspace/how-to-create-a-workspace) # Summary ```bash cd <workspace_directory> cd myserverplugin yo phovea:add-extension # type: namespace, id: hello-rest, filename: hello_rest # extras: namespace=/api/hello_rest # change myserverplugin/hello_rest.py according to the snippet below # start/restart server # access http://localhost:8080/api/hello_rest # or # access http://localhost:9000/api/hello_rest ``` # Tutorial Phovea allows to register additional REST namespaces via the `namespace` extension point. During startup the server looks up all extensions of this extension point and reserves a namespace for it. A namespace in this context is a unique server URL prefix, such as `api/hello_world`. By convention all server namespaces have to start with `/api` followed by unique name. The TDP core itself registers a new REST namespace under `/api/tdp`. Under this namespace all TDP core REST services are located. REST services are implemented using the [Flask framework](http://flask.pocoo.org/). Flask allows to easily map HTTP URLs to python functions. In addition, for convenience Phovea is wrapping and reexporting methods from Flask avoiding a direct dependency on it. Therefore, the important module import reduces to `phovea_server.ns`. ## Register namespace extensions As for every extension, we first need to register a new extension using the Phovea Yeoman generator. ```bash cd <workspace_directory> cd myserverplugin yo phovea:add-extension ``` The extension type is `namespace`. ID something like `hello_rest` and the filename should be `hello_rest`. Following extra parameter are needed: ``` namespace=/api/hello_rest ``` [[images/phovea_rest_api/image1.png]] In this example, we reserve the namespace `/api/hello_rest` such that all HTTP requests starting with `/api/hello_rest` are redirect to the newly created `myserverplugin/hello_rest.py` plugin. In addition, the URL is truncated such that the extension doesn’t have to consider the `/api/hello_rest` prefix. Thus `/api/hello_rest/test` will be mapped to `/test`. The content of `myserverplugin/hello_rest.py` should be replaced with the following snippet ```python from phovea_server.ns import Namespace from phovea_server.util import jsonify import logging app = Namespace(__name__) _log = logging.getLogger(__name__) @app.route('/') def _hello(): return jsonify({ 'message': 'Hello Rest' }) def create(): return app ``` At the beginning the `phovea_server.ns` namespace module is imported which is Phovea wrapper around Flask. In addition the `jsonify` method of `phovea_server.util` is used to convert a Python dictionary/array to a JSON compatible HTTP response. Phovea’s internal `jsonify` method is an improved method of Flasks internal `jsonify` method providing support for serializing numpy and pandas objects, too. In addition the fast `ujson` module is used to serialize the data resulting in a performance improvement. Then, a namespace application object is created being the central management tool for the REST api. The `app` instance is returned as part of the `create` method. The `create` method is used by Phovea as the central point to instantiate a new Python extension. The Namespace class is a wrapper around Flask allowing to use the same methods and annotations to register new URL matching pattern. In this example, a simple pattern matching the root of the REST API is implemented. It returns a simple JSON object containing a simple hello rest message. ## Testing the REST API The newly created REST API can be tested via two different URLs. First via http://localhost:8080/api/hello_rest. Second via http://localhost:9000/api/hello_rest Both return the same result: [[images/phovea_rest_api/image3.png]] [[images/phovea_rest_api/image4.png]] The difference is the following. Internally the Phovea server running within a docker container is accessible via port 9000. In addition, the internal development test server started using `npm start:...` has a configured proxy such that all requests starting with `/api/` are internally proxied to the Phovea server running at port 9000. Thus, http://localhost:8080/api/hello_rest will internally be proxied to http://localhost:9000/api/hello_rest. The proxy within the development server as well as the deployed NGINX server is used to avoid the need for [cross-origin resource sharing (CORS)](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing). Usually when a website is requesting services from another domain such as port 8080 is using services from port 9000 the target server has to explicitly allow this source domain, otherwise the web browser blocks for security reasons the operation. However, by using a proxy both REST API and the website itself are coming from the same domain avoiding this problem. ## Using Request and Path parameters Usually REST API use variable paths and request arguments to generate the response. The following snippet show a simple example how to access request parameters as well as do URL matching. More information can be found in the [Flask documentation](http://flask.pocoo.org/docs/0.12/quickstart/#routing). ```python from phovea_server.ns import Namespace, request, abort # ... @app.route('/greet/<name>') def _greet(name): lang = request.values.get('lang', 'en') template = '' if lang == 'en': template = 'Hello {name}' elif lang == 'de': template = 'Hallo {name}' elif lang == 'es': template = 'Hola {name}' else: abort(400, 'invalid language: ' + lang) return jsonify({ 'message': template.format(name=name) }) # ... def create(): return app ``` First, we have to import additional elements from the `phovea_server.ns` module: `request` for accessing request parameters and `abort` for aborting requests and returning HTTP error codes. The `<name>` syntax is used to perform URL matching. In this case the next URL part after `/greet/` should be provided as a function argument using the name `name`. For example, `/api/hello_rest/greet/datavisyn` will result in executing the `_greet` method with `name=datavisyn` as argument. `request.values` provides access to both GET and POST arguments. Internally it is a Multidict having the same interface as a regular dictionary with additions for handling multiple values for one key. The `.get(name, default_value)` allows to provide default values for a dictionary as opposed to `[name]` which will result in an exception in case the key doesn’t exist. The greeting method accepts the `lang` parameter which specifies in which language the name should be greeted. In case of an unknown language `abort(http_error_code, error_message’)` is used to abort the request and return the HTTP 400 status code = Bad Request. ## Testing REST API 2 [[images/phovea_rest_api/image7.png]] [[images/phovea_rest_api/image6.png]] [[images/phovea_rest_api/image2.png]] --- **Expert knowledge** Flask URL matching logic has several options to specify the type of the path element, for example, `<test:int>` will automatically parse the matching URL part as integer. `<test:path>` is a variant of a string that also allows `/` as part of the pattern, such that `/greet/<name:path>` with `/greet/tom/jones` result in `name=tom/jones` without the `:path` annotation, Flask will throw an error --- # Conclusion Lessons learned: - How to register a new REST API using the namespace extension - How to test the REST API via two different URLs - How to access Request parameters and perform URL matching