Using Tailcall to Embed Existing APIs into GraphQL
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
- Go over to https://dashboard.stripe.com/test/apikeys
- Toggle your Dashboard to Test mode, you don’t wanna spend real bucks to test your API right😷.
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 Mutation
type 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.