Using Tailcall to Embed Existing APIs into GraphQL

Nishant Mishra
6 min readApr 6, 2024

Integrating existing APIs into the GraphQL protocol can be challenging, requiring careful orchestration and management. I’ve discussed about such issues in my previous blog Addressing GraphQL’s Limitations with Tailcall and how Tailcall is managing those problems and giving us a clean and clear path to work on around GraphQL

In this article, we’ll explore how Tailcall simplifies this process and enables developers to seamlessly embed existing APIs into GraphQL.

Understanding Tailcall’s DSL

Tailcall’s Domain-Specific Language (DSL) provides developers with a robust toolkit for managing APIs. Development teams can define caching and batching mechanisms to improve performance and optimise data retrieval. Tailcall’s DSL ensures that data is accessed securely and lawfully by facilitating precise governance and access control.

Step-by-Step Guide to Integration

Configuring Tailcall to work with existing APIs and GraphQL is straightforward. Developers can follow a step-by-step guide to set up caching, batching, and access control strategies, tailoring the integration to their specific requirements.
I’ll be migrating a Stripe API to GraphQL using Tailcall.

Before installing Tailcall and seeing how it works, I’ll setup the Stripe account and get a secret api from the dashboard that we’ll use to fetch and Post requests over their apis

Get Stripe credentials

Getting your apiKey is the 1st task of working with APIs

You’ll find your secret key here, copy it and keep it safe🔐 we’ll use it later

Now let’s jump to what endpoints of Stripe API are we going to migrate over to GraphQL?

We’ll use the Balance, and Customer endpoints i.e to GET Balance, Balance Transactions and GET Customer List, Create, Update and Delete a Customer.

Now let’s setup Tailcall before getting further into Stripe API.

Setting Up Tailcall

To get started with Tailcall, you can install it via npm, ensuring you have Node.js and npm installed on your system. Run the following command to install Tailcall globally:

npm i -g @tailcallhq/tailcall

To verify the correct installation of Tailcall, run:

tailcall

Defining Your GraphQL Schema

Create a .graphql file to define your GraphQL schema. This file will specify the structure of your GraphQL API, including the types and queries that will be exposed.

Here's our schema that integrates with an existing Stripe API:

schema.graphql

# schema.graphql
schema
@server(port: 8000, graphiql: true)
@upstream(baseURL: "https://api.stripe.com/v1/")
@link(src: "./types/balance.graphql", type: Config)
@link(src: "./types/customer.graphql", type: Config) {
query: Query
mutation: Mutation
}

type Query {
# BALANCE
balance: BalanceRoot
@http(
method: "GET"
path: "/balance"
headers: [{ key: "Authorization", value: "Bearer {{env.AUTH_TOKEN}}" }]
)

balancetrn: BalanceTransactionRoot
@http(
method: "GET"
path: "/balance_transactions"
headers: [{ key: "Authorization", value: "Bearer {{env.AUTH_TOKEN}}" }]
)

balanceData(id: String!): BalanceTransactionObject
@http(
method: "GET"
path: "/balance_transactions/{{args.id}}"
headers: [{ key: "Authorization", value: "Bearer {{env.AUTH_TOKEN}}" }]
)

# CUSTOMER
retrieveCustomer(id: String!): CreateCustomerObject
@http(
method: "GET"
path: "/customers/{{args.id}}"
headers: [{ key: "Authorization", value: "Bearer {{env.AUTH_TOKEN}}" }]
)

listAllCustomer: CustomerRoot
@http(
method: "GET"
path: "/customers"
headers: [{ key: "Authorization", value: "Bearer {{env.AUTH_TOKEN}}" }]
)
}

type Mutation {
# CUSTOMERS
addCustomer(input: CreateCustomerInput): CreateCustomerObject
@http(
method: "POST"
path: "/customers"
encoding: "ApplicationXWwwFormUrlencoded"
headers: [{ key: "Authorization", value: "Bearer {{env.AUTH_TOKEN}}" }]
body: "{{args.input}}"
)

updateCustomer(input: CreateCustomerInput, id: String!): CreateCustomerObject
@http(
method: "POST"
path: "/customers/{{args.id}}"
encoding: "ApplicationXWwwFormUrlencoded"
headers: [{ key: "Authorization", value: "Bearer {{env.AUTH_TOKEN}}" }]
body: "{{args.input}}"
)

deleteCustomer(id: String!): DeleteCustomer
@http(
method: "DELETE"
path: "/customers/{{args.id}}"
encoding: "ApplicationXWwwFormUrlencoded"
headers: [{ key: "Authorization", value: "Bearer {{env.AUTH_TOKEN}}" }]
)
}

In this schema, we define a Query and a Mutationtype with multiple fields of the endpoints: balance and customers. Each field is annotated with the @http directive, specifying the path to the corresponding REST API endpoint.

Now we’ll add the types that defines the structure of the data returned by these endpoints.

balance.graphql

# Balance Schema

type Balance {
amount: Int
currency: String
source_types: SourceTypes
}

type SourceTypes {
card: Int
}

type ConnectReserved {
amount: Int
currency: String
}

type BalanceRoot {
object: String
livemode: Boolean
pending: [Balance]
connect_reserved: [ConnectReserved]
available: [Balance]
}

# Balance Transaction

type BalanceTransactionRoot{
object: String
has_more: Boolean
url: String
data: [BalanceTransactionObject]
}

type BalanceTransactionObject {
id: String
object: String
amount: Int
available_on: Int
created: Int
cross_border_classification: String
currency: String
description: String
exchange_rate: String
fee: Int
net: Int
reporting_category: String
source: String
status: String
type: String
fee_details: [FeeDetails]
}

type FeeDetails {
amount: Int
application: String
currency: String
description: String
type: String
}

This above defines the structure of the data provided by the balance endpoints.

customer.graphql

# Customer Schema

type CustomerRoot{
object: String
has_more: Boolean
url: String
data: [CreateCustomerObject]
}

type CreateCustomerObject {
id: String
object: String
address: String
balance: Int
created: Int
currency: String
default_source: String
delinquent: Boolean
description: String
discount: String
email: String
invoice_prefix: String
livemode: Boolean
name: String
next_invoice_sequence: Int
phone: String
shipping: String
tax_exempt: String
test_clock: String
preferred_locales: [String]
invoice_settings: InvoiceSettings
}

type InvoiceSettings {
custom_fields: String
default_payment_method: String
footer: String
rendering_options: String
}

type DeleteCustomer{
id: String
object: String
deleted: Boolean
}

input CreateCustomerInput {
address: String
description: String
email: String
name: String
phone: String
}

Similarly this defines the structure of the data provided by the customer endpoints.

Now after this is done, add an .env file in ur project and add the secret key u saved from ur Stripe Dashboard

AUTH_TOKEN= "your-secret-stripe-api"

Starting the Tailcall Server

With your schema defined, start the Tailcall server by running the following command:

tailcall start ./schema.graphql

This command starts the Tailcall server, which will serve your GraphQL API on port 8000. The graphiql: true option enables the GraphiQL interface, allowing you to interactively explore your API.

If the command succeeds, you should see logs like the following below.

INFO Env file: "/Users/apple/Docs/Projects/tailcall/Stripe-tailcall/.env" loaded 
INFO File read: ./schema.graphql ... ok
INFO File read: ././types/balance.graphql ... ok
INFO File read: ././types/customer.graphql ... ok
INFO N + 1 detected: 0
INFO 🚀 Tailcall launched at [127.0.0.1:8000] over HTTP/1.1
INFO 🌍 Playground: http://127.0.0.1:8000

The server starts with the schema provided and prints out a load of meta information. We will cover those in detail in a bit. For now, open the playground URL in a new tab in your browser and try it out for yourself!

Executing the Query

Once your tailcall server is up and running, open your localhost port 8000 and you will find a GrapiQL interface.
One can write queries here to explore the API that we just migrated from being REST to GraphQL now.

In the query editor of GraphiQL, enter the following query

query{
balance{
object
available{
amount
currency
source_types{
card
}
}
livemode
pending{
amount
currency
source_types{
card
}
}
}
}

After running the query in GraphiQL, you will find the output to be a JSON response structured like this:

{
"data": {
"balance": {
"object": "balance",
"available": [
{
"amount": 0,
"currency": "inr",
"source_types": {
"card": 0
}
}
],
"livemode": false,
"pending": [
{
"amount": 964600,
"currency": "inr",
"source_types": {
"card": 964600
}
}
]
}
}
}

This is the data object that we’ve recieved from the API, and using GraphQL we have fetched only the necessary data that we wished for! You can now add more fields, and compose more queries together!

Congratulations!!!

Play around with Tailcall and build high-performance GraphQL APIs on top of existing REST endpoints🎉.

Conclusion

Tailcall offers a powerful and efficient way to embed existing APIs into a GraphQL layer, simplifying API management and enabling developers to focus on building applications rather than managing APIs. By leveraging Tailcall’s DSL and following best practices, you can create a robust, scalable, and secure GraphQL API that seamlessly integrates with your existing REST APIs.

--

--

Nishant Mishra
Nishant Mishra

Written by Nishant Mishra

Founding Developer Relations Engineer at Tailcall

No responses yet