Skip to content

How to Create a GraphQL Rest API Wrapper and Enhance Your Data

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Intro

Today we will talk about wrapping a REST API with a GraphQL wrapper, which means that the REST API will be accessible via a different GraphQL API. We’ll be using Apollo Server for the implementation. This article assumes you have a basic understanding of REST API endpoints, and some knowledge of GraphQL. Here is the code repo if you want to review it while reading. With that said, we will be looking at why you would wrap a REST API, how to wrap an existing REST API, and how you can enhance your data using GraphQL.

Why wrap a REST API with GraphQL

There are a couple of different reasons to wrap a REST API. The first is migrating from an existing REST API, which you can learn about in detail here, and the second is creating a better wrapper for existing data.

Granted, this can be done using REST. But for this article, we will focus on a GraphQL version. A reason for creating a better wrapper would be using a CMS that provides custom fields. For instance, you get a field that is listed as C_435251, and it has a value of 532. This doesn’t mean anything to us. But when looking at the CMS these values could indicate something like “Breakfast Reservation” is set to “No”. So, with our wrapping, we can return it to a more readable value. Another example is connecting related types. For instance, in the code repo for this blog, we have a type Person with a connection to the type Planet.

Connection example

type Person {
    """The name of this person."""
    name: String

    """A planet that this person was born on or inhabits."""
    homeworld: Planet
}

type Planet {
    """The name of this planet."""
    name: String
}

How to Wrap a REST API

Alright, you have your REST API, and you might wonder how to wrap it with GraphQL? First, you will call your REST API endpoint, which is inside your rest-api-sources file inside your StarwarsAPI class.

REST API example

class StarwarsAPI {
    constructor() {
        this.axios = axios.create({
            baseURL: 'https://swapi.dev/api/',
        });
    }

    async getPerson(id) {
        const {
            data
        } = await this.axios.get(`people/${id}`);
        return data
    }

    async getHomeworld(id) {
        const {
            data
        } = await this.axios.get(`planets/${id}`);
        return data
    }
}

This above class will then be imported and used in the server/index file to set up your new Apollo server.

Apollo server example

const StarwarsAPI = require('./rest-api-sources/starwars-rest-api');

const server = new ApolloServer({
    typeDefs,
    resolvers,
    dataSources: () => ({}),
    context: () => {
        return {
            starwarsAPI: new StarwarsAPI(),
        };
    },
});

Now, in your GraphQL resolver, you will make a person query and retrieve your starWarsAPI from it, which contains the information you want to call. GraphQL resolver

const resolvers = {
    Query: {
        person: async (_, {
            id
        }, {
            starwarsAPI
        }) => {
            return await starwarsAPI.getPerson(id);
        },
    },

};

With the above done, let's start on how to enhance your data in the resolver.

Enhancing your data

With our resolver up and running, we’ll now use it to enhance some of our data. For now, we’ll make the name we get back returned in a first name, and the last initial format. To do so above our Query, we’ll start a Person object and put the variable name inside it. We’ll then grab the name from our Query and proceed to tweak it into the format we want.

Enhancing in resolver

Person: {
    name: ({
        name
    }) => {
        if (!name) {
            return null;
        }
        const [first, last] = name.split(" ")

        if (last === undefined) {
            return first
        }
        return `${first} ${last[0].toUpperCase()}.`
    }
},

Tada! Now, when we call our GraphQL, our name will return formatted in a first name, and last initial state.

Conclusion

Today's article covered why you want to wrap a REST API with GraphQL for migration or to provide a better API layer, how to wrap an existing REST API with GraphQL, and how you can use the resolver to enhance your data for things like name formatting. I hope it was helpful, and will give others a good starting point.

If you want to learn more about GraphQL and REST API wrappers, read up on our resources available at graphql.framework.dev.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

How to Leverage Apollo Client Fetch Policies Like the Pros cover image

How to Leverage Apollo Client Fetch Policies Like the Pros

Apollo Client provides a rich ecosystem and cache for interfacing with your GraphQL APIs. You write your query and leverage the useQuery hook to fetch your data. It provides you with some state context and eventually resolves your query. That data is stored in a local, normalized, in-memory cache, which allows Apollo Client to respond to most previously run requests near instantaneously. This has huge benefits for client performance and the feel of your apps. However, sometimes Apollo's default doesn't match the user experience you want to provide. They provide fetch policies to allow you to control this behavior on each query you execute. In this article, we'll explore the different fetch policies and how you should leverage them in your application. cache-first This is the default for Apollo Client. Apollo will execute your query against the cache. If the cache can fully fulfill the request, then that's it, and we return to the client. If it can only partially match your request or cannot find any of the related data, the query will be run against your GraphQL server. The response is cached for the next query and returned to the handler. This method prioritizes minimizing the number of requests sent to your server. However, it has an adverse effect on data that changes regularly. Think of your social media feeds - they typically contain constantly changing information as new posts are generated. Or a real-time dashboard app tracking data as it moves through a system. cache-first is probably not the best policy for these use cases as you won't fetch the latest data from the upstream source. You can lower the cache time of items for the dashboard to avoid the staleness issue and still minimize the requests being made, but this problem will persist for social media feeds. The cache-first policy should be considered for data that does not change often in your system or data that the current user fully controls. Data that doesn't change often is easily cached, and that's a recommended pattern. For data that the user controls, we need to consider how that data changes. If only the current user can change it, we have 2 options: Return the updated data in the response of any mutation which is used to update the cache Use cache invalidation methods like refetchQueries or onQueryUpdated These methods will ensure that our cache stays in sync with our server allowing the policy to work optimally. However, if other users in the system can make changes that impact the current user's view, then we can not invalidate the cache properly using these strategies which makes this policy unideal. network-only This policy skips the cache lookup and goes to the server to fetch the results. The results are stored in the cache for other operations to leverage. Going back to the example I gave in my explanation cache-first of a social media feed, the network-only policy would be a great way to implement the feed itself as it's ever-changing, and we'll likely even want to poll for changes every 10s or so. The following is an example of what this component could look like: ` Whenever this SocialFeed component is rendered, we always fetch the latest results from the GraphQL server ensuring we're looking at the current data. The results are put in the cache which we can leverage in some children components. cache-only cache-only only checks the cache for the requested data and never hits the server. It throws an error if the specified cache items cannot be found. At first glance, this cache policy may seem unhelpful because it's unclear if our cache is seeded with our data. However, in combination with the network-only policy above, this policy becomes helpful. This policy is meant for components down tree from network-only level query. This method is for you if you're a fan of React components' compatibility. We can modify the return of our previous example to be as follows: ` Notice we're not passing the full post object as a prop. This simplifies our Post component types and makes later refactors easier. The Post would like like the following: ` In this query, we're grabbing the data directly from our cache every time because our top-level query should have fetched it. Now, a small bug here makes maintainability a bit harder. Our top-level GetFeed query doesn't guarantee fetching the same fields. Notice how our Post component exports a fragment. Fragments are a feature Apollo supports to share query elements across operations. In our SocialFeed component, we can change our query to be: ` Now, as we change our Post to use new fields and display different data, the refactoring is restricted to just that component, and the upstream components will detect the changes and handle them for us making our codebase more maintainable. Because the upstream component is always fetching from the network, we can trust that the cache will have our data, making this component safe to render. With these examples, though, our users will likely have to see a loading spinner or state on every render unless we add some server rendering. cache-and-network This is where cache-and-network comes to play. With this policy, Apollo Client will run your query against your cache and your GraphQL server. This further simplifies our example above if we want to provide the last fetched results to the user but then update the feed immediately upon gathering the latest data. This is similar to what X/Twitter does when you reload the app. You'll see the last value that was in the cache then it'll render the network values when ready. This can cause a jarring user experience though, if the data is changing a lot over time, so I recommend using this methodology sparsely. However, if you wanted to update our existing example, we'd just change our SocialFeed component to use this policy, and that'll keep our client and server better in sync while still enabling 10s polling. no-cache This policy is very similar to the network-only policy, except it bypasses the local cache entirely. In our previous example, we wrote engagement as a sub-selector on a Post and stored fields there. These metrics can change in real time pretty drastically. Chat features, reactions, viewership numbers, etc., are all types of data that may change in real time. The no-cache policy is good when this type of data is active, such as during a live stream or within the first few hours of a post going out. You may typically want to use the cache-and-network policy eventually but during that active period, you'll probably want to use no-cache so your consumers can trust your data. I'd probably recommend changing your server to split these queries and run different policies for the operations for performance reasons. I haven't mentioned this yet, but you can make the fetch policy on a query dynamic, meaning you combine these different policies' pending states. This could look like the following: ` We pass whether the event is live to the component that then leverages that info to determine if we should cache or not when fetching the chat. That being said, we should consider using subscription operations for this type of feature as well, but that's an exercise for another blog post. standby This is the most uncommon fetch policy, but has a lot of use. This option runs like a cache-first query when it executes. However, by default, this query does not run and is treated like a "skip" until it is manually triggered by a refetch or updateQueries caller. You can achieve similar results by leveraging the useLazyQuery operator, but this maintains the same behavior as other useQuery operators so you'll have more consistency among your components. This method is primarily used for operations pending other queries to finish or when you want to trigger the caller on a mutation. Think about a dashboard with many filters that need to be applied before your query executes. The standby fetch policy can wait until the user hits the Apply or Submit button to execute the operation then calls a await client.refetchQueries({ include: ["DashboardQuery"] }), which will then allow your component to pull in the parameters for your operation and execute it. Again, you could achieve this with useLazyQuery so it's really up to you and your team how you want to approach this problem. To avoid learning 2 ways, though, I recommend picking just one path. Conclusion Apollo Client's fetch policies are a versatile and helpful tool for managing your application data and keeping it in sync with your GraphQL server. In general, you should use the defaults provided by the library, but think about the user experience you want to provide. This will help you determine which policy best meets your needs. Leveraging tools like fragments will enable you to manage your application and use composable patterns more effectively. With the rise of React Server Components and other similar patterns, you'll need to be wary of how that impacts your Apollo Client strategy. However, if you're on a legacy application that leverages traditional SSR patterns, Apollo allows you to pre-render queries on the server and their related cache. When you combine these technologies, you'll find that your apps perform great, and your users will be delighted....

Efficiently Extract Object References in Shopify Storefront GraphQL API cover image

Efficiently Extract Object References in Shopify Storefront GraphQL API

Efficiently Extract Object References in Shopify Storefront GraphQL API Introduction So, this blog post is born out of necessity and a bit of frustration. If you're diving into the world of Shopify's Storefront API, you've probably realized that while it's powerful, extracting data in the object reference from Metadata fields or Metaobjects (in the GraphQL query) can be a bit like searching for a needle in a haystack. This complexity often arises not from the API's lack of capabilities but from the sparse and sometimes unclear documentation on this specific aspect. That's precisely why I decided to create this post. As a developer, I found myself in a situation where the documentation and community resources were either scarce or not detailed enough for the specific challenges I faced. This guide is the result of my journey - from confusion to clarity. The Situation To understand the crux of my challenge, it's essential to recognize that creating metafields and metaobjects is a common practice for those seeking a more customized and controlled experience with Shopify CMS. In my specific case, I wanted to enrich the information available for each product's vendor beyond what Shopify typically allows, which is just a single text box. I aimed to have each vendor display their name and two versions of their logo: a themed logo that aligns with my website's color scheme and an original logo for use on specific pages. The challenge emerged when I fetched a list of all vendors to display on a page. My GraphQL query for the Storefront API looked like this: ` This was when I hit a roadblock. How do I fetch a field with a more complex type than a simple text or number, like an image? To retrieve the correct data, what specific details must I include in the originalLogo and themedLogo fields? In my quest for a solution, I turned to every resource I could think of. I combed through the Storefront API documentation, searched endlessly on Stack Overflow, and browsed various tech forums. Despite all these efforts, I couldn’t find the clear, detailed answers I needed. It felt like I was looking for something that should be there but wasn’t. Solution Before diving into the solution, it's important to note that this is the method I discovered through trial and error. There might be other approaches, but I want to share the process that worked for me without clear documentation. My first step was to understand the nature of the data returned by the Storefront API. I inspected the value of a metaobject, which looked something like this: ` The key here was the gid, or global unique identifier. What stood out was that it always includes the object type, in this case, MediaImage. This was crucial because it indicated which union to use and what properties to query from this object in the Storefront API documentation. So, I modified my query to include a reference to this object type, focusing on the originalLogo field as an example: ` The next step was to consult the Storefront API documentation for MediaImage at Shopify API Documentation. Here, I discovered the image field within MediaImage, an object containing the url field. With this information, I updated my query: ` Finally, when executing this query, the output for a single object was as follows: ` Through this process, I successfully extracted the necessary data from the object references in the metafields, specifically handling more complex data types like images. Conclusion In wrapping up, it's vital to emphasize that while this guide focused on extracting MediaImage data from Shopify's Storefront API, the methodology I've outlined is broadly applicable. The key is understanding the structure of the gid (global unique identifier) and using it to identify the correct object types within your GraphQL queries. Whether you're dealing with images or any other data type defined in Shopify's Storefront API, this approach can be your compass. Dive into the API documentation, identify the object types relevant to your needs, and adapt your queries accordingly. It's a versatile strategy that can be tailored to suit many requirements. Remember, the world of APIs and e-commerce is constantly evolving, and staying adaptable and resourceful is crucial. This journey has been a testament to the power of perseverance and creative problem-solving in the face of technical challenges. May your ventures into Shopify's Storefront API be equally rewarding and insightful....

Setting Up a Shopify App: Updating Customer Orders with Tracking Info  cover image

Setting Up a Shopify App: Updating Customer Orders with Tracking Info

Today, we are wrapping up our adventure! Last time we learned about retrieving fulfillment IDs, but this time, we encounter the final boss: updating customer orders with tracking information. Now, if we have any new adventurers with us, I recommend heading over here to prep yourself for the encounter ahead. If you just need a recap of our last session, you can head over here. Alternatively, if you just want the code from last time that can be found here. If you want to skip ahead and look at the code it can be found here. With that all said we’re off to battle with updating customer orders with tracking information! Body We’re gonna start by heading over to our app/routes/app.index.jsx file, and grabbing the code found in the loader function. We’ll be moving that to our action function so we can add our post call. We’ll completely replace the existing action function code, and because of that, we need to make a couple of tweaks to the code base. We’re going to remove anything that has a reference to actionData?.product or productId. Now what we need to add the call admin.rest.resources.Fulfillment, which will allow us to update customer orders with tracking information. We’ll be placing it under our fulfillment ID loop. Here is a general example of what that call will look like. ` This is a good start as we now have our fulfillment information and get to add a few things to it. We’ll start off by adding our fulfillment ID and then our fulfillment tracking info. ` Awesome! Now we have given the fulfillment ID and tracking info we need to the fulfillment object, but we need to do one more thing for that to update. Thankfully, it’s a small thing and that’s to save it. ` Now, the above will work wonderfully for a single order, but based on our prior adventurers, we had multiple ids for orders that needed to be completed. So our next step is to loop over our fulfillment object. Though before we do that, here is what the current code should look like: ` Before we go to loop over this, we’re going to add a small change to fulfillmentIds. We’re going to create a new variable and add the company and tracking number information. So above the fulfillment variable, we will add this: ` Perfect! Now for the looping, we’ll just wrap it in a for of loop: ` Now that the loop is set up, we will be able to go through all of the orders and update them with the shipping company and a tracking number. So we’ll run a yarn dev, and navigate to the app page by pressing the p button. We should see our template page, and be able to click on the Generate a product button. Now we’ll navigate to our order page and we should see all open orders set to fulfilled. Conclusion Here we are. At the end of our three part saga, we covered a fair share of things in order to get our customer orders tracking information added, and can now take a long rest to rejuvenate....

“It Sounds a Little Dystopian, But Also Kind of Amazing”: Conversations on Long Term AI Agents and "Winning" Product Hunt with Ellie Zubrowski cover image

“It Sounds a Little Dystopian, But Also Kind of Amazing”: Conversations on Long Term AI Agents and "Winning" Product Hunt with Ellie Zubrowski

Ellie Zubrowski doesn’t walk a traditional path. In the three years since graduating from a university program in Business Administration, she biked across the U.S., studied Kung Fu in China, learned Mandarin just for fun, and completed the #100DaysOfCode challenge after deciding she wanted a career switch. That same sense of curiosity and willingness to jump into the unknown now fuels her work as a Developer Advocate at Pieces, where she leads product launches, mentors job seekers, and helps developers learn how to best leverage Pieces’ Long-Term Memory Agent. Her journey into tech was guided not just by a want to learn how to code and break into the industry, but by a fascination with the structure of language itself. > “There are so many parallels between human languages and programming languages,” she says. “That realization really made me fall in love with software.” > We spoke with Ellie about launching a #1 Product Hunt release, her predictions for AI agents, and why conferences don’t have to break your budget. Launching LTM-2 to the Top of Product Hunt Recently, Ellie led the launch of Pieces’ Long-Term Memory Agent (LTM-2), which took the top spot on Product Hunt—a major win for the team and their community. > “I’m super competitive,” she admits. “So I really wanted us to win.” The launch was fully organic—no paid promotions, just coordinated team efforts, a well-prepared content pipeline, and an ambassador program that brought in authentic engagement across X, Discord, and Reddit. She documented their entire strategy in this blog post, and credits the success not just to good planning but to a passionate developer community that believed in the product. Following a successful performance at Product Hunt, Ellie is committed to keeping Pieces’ user community engaged and contributing to its technological ecosystem. > “Although I’m still fairly new to DevRel (coming up on a year at Pieces!), I think success comes down to a few things: developer adoption and retention, user feedback, community engagement, and maintaining communication with engineering.” Why AI Agents Are the Next Big Thing Ellie sees a major shift on the horizon: AI that doesn’t wait for a prompt. > “The biggest trend of 2025 seems to be AI agents,” she explains, “or AI that acts proactively instead of reactively.” Until now, most of us have had to tell AI exactly what to do—whether that’s drafting emails, debugging code, or generating images. But Ellie imagines a near future where AI tools act more like intelligent teammates than assistants—running locally, deeply personalized, and working in the background to handle the repetitive stuff. > “Imagine something that knows how you work and quietly handles your busy work while you focus on the creative parts,” she says. “It sounds a little dystopian, but also kind of amazing.” Whether we hit that level of autonomy in 2025 or (likely) have to wait until 2026, she believes the move toward agentic AI is inevitable—and it’s changing how developers think about productivity, ownership, and trust. You can read more of Ellie’s 2025 LLM predictions here! The Secret to Free Conferences (and Winning the GitHub Claw Machine) Ellie will be the first to tell you: attending a tech conference can be a total game-changer. “Attending my first tech conference completely changed my career trajectory,” she says. “It honestly changed my life.” And the best part? You might not even need to pay for a ticket. > “Most conferences offer scholarship tickets,” Ellie explains. “And if you’re active in dev communities, there are always giveaways. You just have to know where to look.” In her early days of job hunting, Ellie made it to multiple conferences for free (minus travel and lodging)—which she recommends to anyone trying to break into tech. Also, she lives for conference swag. One of her all-time favorite moments? Winning a GitHub Octocat from the claw machine at RenderATL. > “She’s one of my prized possessions,” Ellie laughs. Proof here. 🐙 Her advice: if you’re even a little curious about going to a conference—go. Show up. Say hi to someone new. You never know what connection might shape your next step. Ellie’s Journeys Away from her Desk Earlier this year, Ellie took a break from product launches and developer events to visit China for Chinese New Year with her boyfriend’s family—and turned the trip into a mix of sightseeing, food adventures, and a personal mission: document every cat she met. (You can follow the full feline thread here 🐱) The trip took them through Beijing, Nanjing, Taiyuan, Yuci, Zhùmǎdiàn, and Yangzhou, where they explored palaces, museums, and even soaked in a hot spring once reserved for emperors. > “Fancy, right?” Ellie jokes. But the real highlight? The food. > “China has some of the best food in the world,” she says. “And lucky for me, my boyfriend’s dad is an amazing cook—every meal felt like a five-star experience.” What’s Next? With a YouTube series on the way, thousands of developers reached through her workshops, and an eye on the next generation of AI tooling, Ellie Zubrowski is loving her experience as a developer advocate. Follow @elliezub on X to stay in the loop on her work, travels, tech experiments, and the occasional Octocat sighting. She’s building in public, cheering on other devs, and always down to share what she’s learning along the way. Learn more about Pieces, the long-term LLM agent....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co