Source: https://dev-docs.nebula-graph.io/v5.3.2/app-development/rest-api/ Documentation version: v5.3.2 # Build a service with the REST API Build a movie cast service with the REST API and FastAPI. The application accepts a movie ID, calls the NebulaGraph Database Developer Edition API, and returns JSON for the movie and its actors. The REST API is a preview feature for evaluation and testing. See [REST API](https://dev-docs.nebula-graph.io/v5.3.2/apis/) for the interface reference. ## Before you start - Follow [Prepare NebulaGraph Database Developer Edition](https://dev-docs.nebula-graph.io/v5.3.2/app-development/#prepare-database) to start NebulaGraph Database Developer Edition and load the `movie` sample dataset. - Install Python 3.10 or later and know Python functions, dictionaries, and exception handling. - Have a NebulaGraph Database Developer Edition username and password. The application computer must be able to reach the NebulaGraph Database Developer Edition HTTP port. The terminal commands below are for macOS and Linux. Run them on the computer running the application, which can be the development machine running Docker or another computer that can reach NebulaGraph Database Developer Edition. ## 1. Create the project and install dependencies ```` mkdir movie-rest-app cd movie-rest-app python3 -m venv .venv source .venv/bin/activate python -m pip install httpx==0.28.1 fastapi==0.142.1 uvicorn==0.54.0 ```` HTTPX sends HTTP requests to NebulaGraph Database Developer Edition, FastAPI provides the application HTTP endpoint, and Uvicorn runs the application. ## 2. Configure the NebulaGraph Database Developer Edition connection Choose the NebulaGraph Database Developer Edition address for your application computer: | Application location | NebulaGraph Database Developer Edition address | On the same development machine as Docker | `http://127.0.0.1:19669` | On another computer | `http://:19669` Replace `` with the IP address of the machine running Docker. If you changed the `19669:19669` mapping, use the development machine port to the left of the colon. Set these environment variables in the terminal used to create the project. This example assumes that the application and Docker run on the same machine: ```` export NEBULA_HTTP_URL='http://127.0.0.1:19669' export NEBULA_USER='root' export NEBULA_PASSWORD="$(python -c 'import getpass; print(getpass.getpass("Database password: "))')" ```` At the `Database password:` prompt, enter NebulaGraph Database Developer Edition's current, actual password. Enter a single `$` for each character escaped as `$$` in the Compose file. Use this terminal for the startup command below. Set `NEBULA_HTTP_URL` to the root URL of the NebulaGraph Database Developer Edition HTTP API, without `/gql` or `/ui/console`. The supplied configuration uses HTTP. ## 3. Save the application code Download [movie_rest.py](https://dev-docs.nebula-graph.io/v5.3.2/app-development/files/movie_rest.py) and save it in `movie-rest-app`. The example queries `/default_schema/movie`. If your data is in another Schema, update the graph path in both queries in the file. View the complete code ```` """Run with: python -m uvicorn movie_rest:app --host 127.0.0.1 --port 8100.""" import logging import os from typing import Annotated import httpx from fastapi import FastAPI, HTTPException, Path app = FastAPI(title="Movie cast service (REST API)") logger = logging.getLogger(__name__) base_url = os.environ.get("NEBULA_HTTP_URL", "http://127.0.0.1:19669").rstrip("/") username = os.environ.get("NEBULA_USER", "root") password = os.environ["NEBULA_PASSWORD"] class DatabaseError(Exception): pass def post(client, path, body=None): response = client.post(path, json=body) response.raise_for_status() payload = response.json() if not isinstance(payload, dict) or payload.get("code") != "00000": raise DatabaseError("Database returned an unsuccessful response") return payload.get("data", {}) def query_rows(client, statement): data = post(client, "/gql", {"gql": statement}) if not isinstance(data, dict): raise DatabaseError("Missing query data") rows = data.get("tables") if not isinstance(rows, list) or not all(isinstance(row, dict) for row in rows): raise DatabaseError("Invalid query result table") return rows @app.get("/movies/{movie_id}/actors") def get_actors(movie_id: Annotated[int, Path(ge=1, le=9223372036854775807)]): movie_query = ( "USE /default_schema/movie MATCH (m@Movie) " f"WHERE m.id = {movie_id} RETURN m.name AS movie" ) actors_query = ( "USE /default_schema/movie MATCH (a@Actor)-[:Act]->(m@Movie) " f"WHERE m.id = {movie_id} " "RETURN a.id AS actor_id, a.name AS actor ORDER BY actor_id" ) try: with httpx.Client(base_url=base_url, timeout=10.0) as client: auth = post(client, "/auth", {"username": username, "password": password}) token = auth.get("token") if isinstance(auth, dict) else None if not isinstance(token, str) or not token: raise DatabaseError("Missing authentication token") client.headers["Authorization"] = f"Bearer {token}" try: movies = query_rows(client, movie_query) actors = query_rows(client, actors_query) if movies else [] finally: # Release this request's database session even if a query fails. try: post(client, "/signout") except Exception as exc: logger.warning("Signout failed (%s)", type(exc).__name__) except Exception as exc: logger.warning("Database request failed (%s)", type(exc).__name__) raise HTTPException(status_code=502, detail="Database request failed") from exc if not movies: raise HTTPException(status_code=404, detail="Movie not found") return {"movie_id": movie_id, "movie": movies[0]["movie"], "actors": actors} ```` ## 4. Start the application From the project directory, with the virtual environment active, run: ```` python -m uvicorn movie_rest:app --host 127.0.0.1 --port 8100 ```` If port `8100` is in use, stop the application using it or use `--port 8101` and update the URLs below. These messages indicate that the application has started: ```` Application startup complete. Uvicorn running on http://127.0.0.1:8100 (Press CTRL+C to quit) ```` Keep this terminal running. Use a browser or another terminal to call the endpoint. The application listens on local port `8100`; press Ctrl+C to stop it. ## 5. Call the query endpoint On the computer running the application, use either method below to find the movie with ID `3`. If the application runs remotely over SSH, first set up [port forwarding](https://dev-docs.nebula-graph.io/v5.3.2/app-development/rest-api/#ssh-access) as described below. Browsercurl 1. Open `http://127.0.0.1:8100/docs`, the application's generated API documentation. 2. Expand `GET /movies/{movie_id}/actors` and select Try it out. 3. Enter `3` for `movie_id`, select Execute, and inspect the response. Open another terminal on the application computer and run: ```` curl -sS http://127.0.0.1:8100/movies/3/actors | python3 -m json.tool --no-ensure-ascii ```` Both methods should return the same JSON: ```` { "movie_id": 3, "movie": "Shadows in Paradise", "actors": [ {"actor_id": 4826, "actor": "Matti Pellonpää"}, {"actor_id": 4828, "actor": "Sakari Kuosmanen"}, {"actor_id": 5999, "actor": "Kati Outinen"} ] } ```` Access a remote application over SSH In a local terminal on the browser computer, start forwarding. Replace `` and `` with the values used to log in to the application computer: ```` ssh -N -L 8100:127.0.0.1:8100 @ ```` For a nondefault SSH port, add `-p `. After authentication, the forwarding terminal keeps running, usually without output. Keep the remote application terminal and local forwarding terminal running. Open `http://127.0.0.1:8100/docs` in the local browser. Requests to local port `8100` are forwarded to remote application port `8100`. If the local port is occupied, change the value after `-L` to `8101:127.0.0.1:8100` and open `http://127.0.0.1:8101/docs` instead. The application API documentation is at `http://127.0.0.1:8100/docs`. The NebulaGraph Database Developer Edition API reference is at `http://:19669/api-docs`, where `` is the IP address of the machine running Docker. ## Code explanation ### Query and response fields The application queries the movie name first, then its actors. Checking the movie first distinguishes a missing movie from an existing movie with no actors: the former returns `404`; the latter returns an empty `actors` array. The actor query is: ```` USE /default_schema/movie MATCH (a@Actor)-[:Act]->(m@Movie) WHERE m.id = 3 RETURN a.id AS actor_id, a.name AS actor ORDER BY actor_id ```` `(a@Actor)-[:Act]->(m@Movie)` matches an actor's relationship to a movie they act in. The arrow points from actor to movie. `WHERE` filters by movie ID, and `ORDER BY` sorts by actor ID. The application replaces `3` with the validated integer `movie_id`. See [Example dataset: movie](https://dev-docs.nebula-graph.io/v5.3.2/get-started/dataset/) for the model. | Query result | Application response field | `m.name AS movie` in the movie query | `movie` | `a.id AS actor_id` in the actor query | `actor_id` in each item of `actors` | `a.name AS actor` in the actor query | `actor` in each item of `actors` ### Authenticate and send queries For each request, the application: 1. Sends NebulaGraph Database Developer Edition credentials to `POST /auth` and reads the JWT from `data.token`. 2. Sets `Authorization: Bearer ` and sends `{"gql": "query statement"}` to `POST /gql`. 3. Checks the HTTP status and response body `code`. The success code is the string `"00000"`; an HTTP `200` response can still contain a query error. On success, result rows are read from `data.tables`. 4. Calls `POST /signout` after the queries finish or fail to release the authenticated session, then closes the HTTP client. `post()` and `query_rows()` are helpers defined in the complete code to check responses and read result rows: ```` auth = post(client, "/auth", {"username": username, "password": password}) client.headers["Authorization"] = f"Bearer {auth['token']}" movies = query_rows(client, movie_query) actors = query_rows(client, actors_query) if movies else [] ```` `data.headers` lists the result column names; `data.tables` is an array of row objects. The query aliases fix the field names, so the application can read `movie`, `actor_id`, and `actor` directly. Each query specifies the graph with `USE /default_schema/movie`, so no session working graph is required. Put multiline strings directly in the JSON `gql` field, without the `'''` input delimiters used by `ngql`. ## Response status and troubleshooting | HTTP status | Meaning | Action | `200` | The query succeeded | Inspect `movie` and `actors`; the array is empty when there are no actors | `404` | No movie has this ID | Use an ID present in the dataset, such as `3` | `422` | The movie ID has an invalid format or range | Use a positive integer no greater than `9223372036854775807` | `502` | A NebulaGraph Database Developer Edition HTTP request, authentication, or query failed | Check the NebulaGraph Database Developer Edition address and password, then use [Connect and run your first query](https://dev-docs.nebula-graph.io/v5.3.2/get-started/first-query/#select-graph) to confirm access to the data To check input validation, run: ```` curl -i http://127.0.0.1:8100/movies/abc/actors ```` Expect HTTP `422`. The request is rejected before any query is sent to NebulaGraph Database Developer Edition. The application accepts only a movie ID; the code fixes the query structure and graph path. ### When the application is unreachable If the browser reports `ERR_CONNECTION_REFUSED` or `curl` cannot connect, check: - The application terminal is still running. - The request targets the application computer. `127.0.0.1` always refers to the computer running the browser or command. - For SSH access, the local forwarding terminal is still running. Run this command on the browser computer. Use the updated local port if you changed it: ```` curl -I http://127.0.0.1:8100/docs ```` Expect `HTTP/1.1 200 OK`, confirming that the application's API documentation is reachable. Then call the movie endpoint to check the NebulaGraph Database Developer Edition query results. The application terminal logs the exception type for failed NebulaGraph Database Developer Edition requests. Responses sent to callers contain no NebulaGraph Database Developer Edition passwords or JWT tokens. ## Exercise: query directors Copy the `get_actors()` function and its `@app.get` decorator. Name the new function `get_directors()` and change its route to `/movies/{movie_id}/directors`. Keep the movie existence check and replace the actor query with: ```` USE /default_schema/movie MATCH (d@Director)-[:Direct]->(m@Movie) WHERE m.id = 3 RETURN d.id AS director_id, d.name AS director ORDER BY director_id ```` Rename the actor query and result variables for directors. Return a `directors` array, with `director_id` and `director` in each item. Restart the application and call the new endpoint to check the movie and its directors. An existing movie with no director relationships returns an empty `directors` array; a missing movie returns `404`. Run the query in the NebulaGraph Studio console first to compare its result with the application response. ## Continue development Press Ctrl+C to stop the application. NebulaGraph Database Developer Edition continues running and retains its graph data. If you switch terminals before restarting, activate the virtual environment and set the environment variables again. The example authenticates and signs out separately for each request. If your application reuses JWT tokens, handle expiration and renewal after authentication failures. See [Configure user access](https://dev-docs.nebula-graph.io/v5.3.2/server-admin/authorization/configure-user-access/) for NebulaGraph Database Developer Edition privileges. - [REST API](https://dev-docs.nebula-graph.io/v5.3.2/apis/): Read about NebulaGraph Database Developer Edition request and response structures. - [Build a service with the Python SDK](https://dev-docs.nebula-graph.io/v5.3.2/app-development/python-sdk/): Implement the same endpoint through the SDK.