A previous article on this blog introduced GraphQL endpoints within the database. In this article you were promised an updated version with fewer lines of code. Welcome to the update!

By the way, if you are looking for an alternative way to enable GraphQL for your schema, have a look at Oracle REST Data Services (ORDS): ORDS offers GraphQL-support as a feature, documented in Oracle® REST Data Services Developer’s Guide chapter 10. In addition to ORDS you can use GraphQL natively in the database thanks to a new table function introduced in Oracle AI Database 23.26.0.

In addition to simplified code the databaseAPI file has been updated to allow running both outside and within the database. The article assumes you read the previous one for additional background information. The code can be found on GitHub.

An overview of the development environment

This post was written using an Oracle Database 23ai Free database. The database is deployed as a container image, maintained by podman. The image was pulled from Oracle’s container registry.

Rather than using the GraphQL JavaScript reference implementation, the code is based on @graphql-tools/schema, a very convenient abstraction layer. You may have guessed it, all of this is possible thanks to Multilingual Engine (MLE) in Oracle Database 23ai on Linux x86-64.

As always, before using third-party modules in your project, make sure that your security and compliance department give you the go-ahead before starting!

The list of components hasn’t really changed compared to the previous article:

{
      "dependencies": {
        "@graphql-tools/schema": "^10.0.3"
      },
      "type": "module",
      "devDependencies": {
        "@rollup/plugin-commonjs": "^26.0.1",
        "@rollup/plugin-node-resolve": "^15.2.3",
        "oracledb": "^6.5.1",
        "prettier": "^3.2.5",
        "rollup": "^4.18.0"
      }
    }
    

The code was tested using node/20 LTS.

Interestingly, the rollup configuration used in the earlier article ran into an issue trying to create the bundle file for use with the database. To fix this problem (@graphql-tools/merge/esm/typedefs-mergers/merge-nodes.js complained about isNode(), claiming it wasn’t exported by graphql/language/ast.js) the commonjs plugin was added to the configuration.

What’s the difference?

Using the GraphQL JavaScript reference implementation requires you to write a fair bit of code to define the GraphQL schema. It would be a lot nicer if the Schema Definition Language (SDL) could be used in a declarative way instead. This is the main change, compared to the previous article. This is the updated graphql-schema.js file:

import { makeExecutableSchema } from '@graphql-tools/schema';
    import { getLocationById, getLocation, getAllLocations } from './databaseAPI.js';
    
    const typeDefs = `
    """
    this type represents the HR.LOCATIONS table
    """
    type Location {
        location_id:     Int
        street_address:  String
        postal_code:     String
        city:            String
        state_province:  String
        country_id:      String
    }
    
    """
    Each GraphQL schema must at least provide a way to query it. Other
    options include changing (mutation) and subscribing to data. This
    query type defines the 3 API calls and what data is returned.
    """
    type Query {
        getAllLocations: [Location]
        getLocationById(id: Int): Location
        getLocation(street: String, postalCode: String, city: String, stateProvince: String, country:String ): [Location]
    }
    `;
    // a resolver tells GraphQL how to obtain the data 
    const resolvers = {
        Query: {
            getAllLocations: async () => {
                return await getAllLocations();
            },
            getLocationById: async (_, { id }) => {
                return await getLocationById(id);
            },
            getLocation: async (_, { street, postalCode, city, stateProvince, country }) => {
                return await getLocation(street, postalCode, city, stateProvince, country);
            }
        }
    };
    
    // this is where everything is put together: schema, and all resolvers
    export function generateSchema() {
        const executableSchema = makeExecutableSchema({
            typeDefs,
            resolvers
        });
    
        return executableSchema;
    }
    

The above code is much more readable. All type declarations are present in typeDefs. Resolver functions are defined in resolvers and these are both passed to makeExecutableSchema().

As you read earlier it is necessary to change rollup.config.mjs to avoid errors creating the result file. Here it is:

import { nodeResolve } from '@rollup/plugin-node-resolve';
    import commonjs from '@rollup/plugin-commonjs';
    
    export default {
        input: 'src/graphQLQuery.js',
        output: {
            file: 'dist/bundle.js',
            format: 'esm'
        },
        plugins: [
            nodeResolve(),
            commonjs()
        ],
        external: ['mle-js-oracledb', 'oracledb']
    };
    

The database API remains mostly unchanged, an option to test the code using node-oracledb has been added. Otherwise, everything remains the same!

$ sql emily@localhost/freepdb1
    
    
    SQLcl: Release 24.1 Production on Thu Jun 06 10:33:37 2024
    
    Copyright (c) 1982, 2024, Oracle.  All rights reserved.
    
    Password? (**********?) 
    Connected to:
    Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
    Version 23.4.0.24.05
    
    SQL> select
      2    json_serialize(
      3      graphql_query(
      4        'query locById($id: Int) { getLocationById(id: $id) { city country_id } }',
      5        JSON('{id: 1000}')
      6      )
      7      pretty
      8    ) graphql_result
      9* /
    
    GRAPHQL_RESULT                                                                                                 
    __________________________________________________________________________ 
    {
      "data" :
      {
        "getLocationById" :
        {
          "city" : "Roma",
          "country_id" : "IT"
        }
      }
    }    
    

Happy coding!