Skip to content
/ rest-ql Public

Transform your REST APIs into a GraphQL-like powerhouse with type-safe queries, automatic batching, and seamless caching – all without changing your backend.

Notifications You must be signed in to change notification settings

mhdSid/rest-ql

Repository files navigation

RestQL

RestQL is a powerful and flexible library that allows you to query REST APIs using a GraphQL-like syntax. It provides a seamless way to interact with REST endpoints, offering features like batched queries, caching, and data transformation.

Features

  • Intuitive Syntax: RestQL offers a clear and precise syntax, enabling developers to articulate their data requirements succinctly.
  • Built-in Types for Data Structures: RestQL includes robust type definitions that operate at runtime, ensuring accurate data transformations and minimizing the risk of type-related errors
  • Structured API Definition: With support for a well-defined API schema, RestQL facilitates a more organized and maintainable codebase.
  • Batch Processing: By allowing the execution of multiple requests in a single operation, RestQL significantly reduces network latency and server load.
  • Advanced Caching Mechanisms: The library incorporates efficient caching strategies, optimizing both time and resource utilization.
  • Data Transformation: Custom transformations can be applied to tailor the retrieved data to specific application needs.
  • Type Safety: With TypeScript support, RestQL ensures type safety, reducing runtime errors and enhancing code reliability

Installation

npm install lib-restql

or

yarn add lib-restql

Schema Definition Language (SDL)

RestQL uses an SDL to define the structure of your API.

Type Definitions

  type ResourceName {
    field: FieldType @directive
    ...
  }
  • ResourceName: Corresponds to a REST resource (e.g., User, Post)
  • field: Represents a property of the resource
  • FieldType: Can be scalar (String, Int, Boolean, etc.) or another defined type

Directives

  type User {
    id: String @from("user_id") @transform("transformUserId")
    name: String @from("full_name")
    email: String @from("contact_info.email")

    @endpoint(GET, "/users", "data.data[0]")
    @endpoint(POST, "/users", "data.data[0]")
  }
  • @from("api_field_name"): Maps the field to a different name in the API response
  • @transform("transformerName"): Applies a custom transformation to the field
  • @endpoint(METHOD, "path", "dataPath"): Defines REST endpoint for the resource

Query Language

Basic Query Structure

query QueryName {
  resource {
    field1
    field2
    nestedResource {
      nestedField1
    }
  }
}

Query with Arguments

query QueryName($arg: Type) {
  resource(id: $arg) {
    field1
    field2
  }
}

Multiple Resources in One Query

query GetMultipleResources {
  resource1 {
    field1
  }
  resource2 {
    field2
  }
}

Mutation Language

Basic Mutation Structure

mutation MutationName($arg: Type) {
  action(input: $arg) {
    resultField1
    resultField2
  }
}

Example:

mutation CreateUser($name: String!, $email: String!) {
  createUser(name: $name, email: $email) {
    id
    name
    email
  }
}

Nested Data Retrieval

RestQL automatically handles nested data structures:

query GetUserWithPosts {
  user {
    name
    posts {
      title
      comments {
        text
      }
    }
  }
}

Array Fields

Use square brackets to denote array fields:

type User {
  hobbies: [String]
  friends: [User]
}

Nested Arrays:

type User {
  hobbies: [String]
  friends: [[User]]
}

Custom Transformers

Define custom transformers in your RestQL initialization:

const transformers = {
  transformUserId: (originalData, shapedData) => ({
    ...shapedData,
    id: `custom_${shapedData.id}`
  })
};

Execution

Execute queries using the RestQL instance:

const result = await restql.execute(queryString, variables, options);
  • queryString: The RestQL query or mutation
  • variables*: Object containing any variable values
  • options: Additional options like { useCache: true }

Quick Start

import { RestQL } from "lib-restql";

// Define the SDL
const sdl = `
  type User {
    id: String @from("user_id") @transform("transformUserId")
    name: String @from("full_name")
    email: String @from("contact_info.email")
    address: Address @from("location")
    hobbyList: [Hobby] @from("hobbies")
    posts: Post

    @endpoint(GET, "/users", "data.data[0]")
    @endpoint(POST, "/users", "data.data[0]")
    @endpoint(PUT, "/users", "data.data[0]")
    @endpoint(PATCH, "/users", "data.data[0]")
    @endpoint(DELETE, "/users", "data")
  }

  type Post {
    id: String @from("post_id")
    name: String @from("post_name")

    @endpoint(GET, "/posts", "data.data[0]")
    @endpoint(POST, "/posts", "data.data[0]")
    @endpoint(PUT, "/posts", "data.data[0]")
    @endpoint(PATCH, "/posts", "data.data[0]")
    @endpoint(DELETE, "/posts", "data")
  }

  type Address {
    street: String @from("street_name")
    city: String
    country: Country @from("country")
  }

  type Country {
    name: String @from("country_name")
    capital: String @from("capital_name")
    blockList: [[Block]] @from("blocks")
  }

  type Block {
    name: String @from("block_name")
    number: String @from("block_number")
  }

  type Hobby {
    name: String @from("hobby_name")
    id: String @from("hobby_id")
  }
`;

// Define base URLs
const baseUrls = {
  default: "https://api.example.com",
  // "/users": "https://users.api.example.com" // optional
};

// Define options
const options = {
  cacheTimeout: 300000, // 5 minutes
  headers: {
    Authorization: "Bearer your-token-here"
  },
  maxRetries: 3,
  retryDelay: 1000,
  batchInterval: 50
};

// Define transformers
const transformers = {
  transformUserId: (originalData: any, shapedData: any) => {
    return {
      ...shapedData,
      id: `(ID: ${shapedData.id})`
    };
  }
};

// Initialize RestQL
const restql = new RestQL(sdl, baseUrls, options, transformers);

// Define one or many queries
const query = `
  query GetUserWithPosts($userId: String!, $postLimit: Int) {
    user(userId: $userId) {
      id
      name
      email
      parkName
      hobbyList {
        id
        name
      }
      address {
        street
        city
        country {
          name
          capital
          blockList {
            name
            number
            parkList {
              parkName
              parkId
            }
          }
        }
      }
      posts(postLimit: $postLimit) {
        id
        name
        searchResult {
          searchDesc
          searchType
        }
      }
    }
  }
`;

// Execute the query
async function executeQuery() {
  try {
    const result = await restql.execute(query, {
      userId: "123",
      postLimit: 5,
    }, { useCache: true });
    console.log("Query result:", result);

    // Caching
    const resultCached = await restql.execute(query, {}, { useCache: true });
    console.log("Query result cached:", resultCached);
  } catch (error) {
    console.error("Error executing query:", error);
  }
}

executeQuery();

Advanced Usage

Mutations

const batchedMutations = `
  mutation CreateUsers($name: String!, $email: String!) {
    createUser(name: $name, email: $email) {
      id
      name
      email
      address {
        street
        city
        country {
          name
          capital
          blockList {
            name
            number
          }
        }
      }
      hobbyList {
        name
        id
      }
    }
    createUser(name: $name, email: $email) {
      id
      name
      email
    }
  }
`;

async function createUsers() {
  try {
    const result = await restQL.execute(batchedMutations, { name1: 'name_1', emai1: 'email_1', name2: 'name_2', email2: 'email_2' });
    console.log('Created users:', result);
  } catch (error) {
    console.error('Error creating user:', error);
  }
}

Data Mapping Example

RestQL excels at mapping complex REST API responses to the structure defined in your GraphQL-like queries. Here's an example of how RestQL transforms API data:

API Responses

Users API Response:

{
  "data": {
    "data": [
      {
        "user_id": 1,
        "full_name": "test_full_name",
        "contact_info": {
          "email": "[email protected]"
        },
        "location": {
          "street_name": "test_street_name",
          "city": "test_city",
          "country": {
            "country_name": "test_country_name",
            "capital_name": "test_capital",
            "blocks": [
              [{
                "block_name": "test_block_name_1",
                "block_number": "test_block_number_1"
              }, {
                "block_name": "test_block_name_2",
                "block_number": "test_block_number_2"
              }]
            ]
          }
        },
        "hobbies": [
          {
            "hobby_name": "kate",
            "hobby_id": 1
          }
        ]
      }
    ]
  }
}

Posts API Response:

{
  "data": {
    "data": [
      {
        "post_id": 1,
        "post_name": 1
      }
    ]
  }
}

RestQL Query:

query {
  user {
    id
    name
    email
    address {
      street
      city
      country {
        name
        capital
        blockList {
          name
          number
        }
      }
    }
    hobbyList {
      name
      id
    }
  }
  post {
    id
    name
  }
}

Resulting Data Structure:

{
  "post": {
    "id": 1,
    "name": 1
  },
  "user": {
    "id": "idddd_shapedData_1",
    "name": "test_full_name",
    "email": "[email protected]",
    "address": {
      "street": "test_street_name",
      "city": "test_city",
      "country": {
        "name": "test_country_name",
        "capital": "test_capital",
        "blockList": [
          [
            {
              "name": "test_block_name_1",
              "number": "test_block_number_1"
            },
            {
              "name": "test_block_name_2",
              "number": "test_block_number_2"
            }
          ]
        ]
      }
    },
    "hobbyList": [
      {
        "name": "kate",
        "id": 1
      }
    ]
  }
}

Documentation

WIP

Contributing

We welcome contributions! Please see our contributing guidelines for more details.

License

RestQL is MIT licensed. This markdown provides an introduction to your RestQL project, highlighting its key features, providing a quick start guide, and showing some advanced usage examples. You may want to adjust the content based on the specific details of your implementation, add more examples, or include additional sections as needed.

Remember to create the referenced files like CONTRIBUTING.md and LICENSE, and replace link-to-your-docs with the actual link to your documentation if you have one.

About

Transform your REST APIs into a GraphQL-like powerhouse with type-safe queries, automatic batching, and seamless caching – all without changing your backend.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published