Skip to content

Class and Enum Typings for Handling Data with GraphQL

Intro

Today we’ll talk about class and enum typings for handling more complex data with GraphQL. The typings will be used on class objects to make them easier to read and understand. This blog will build on the concepts of another blog found here, in which I discussed wrapping and enhancing data, which talks about how to create a GraphQL Rest API wrapper, and how to enhance your data.

This article assumes you have a basic understanding of classes, typing, and enums. Here is the code repo if you want to review it while reading, or just skip ahead. With that said, we will look at the class structure we’re going to be using, the enums, and the type lookup for our customField enum with our mapper.

Class setup

This class we’re going to set up will be strictly for typing. We’re going to make a RestaurantGuest class as this data will be restaurant themed. So in our restaurant.ts file, we will name it RestaurantGuest, and include a few different items.

Basic starting class example

  class RestaurantGuest {
    id!: number;
    name!: string;
    email!: string;
    phoneNumber!: string;
    /** foodReservation */
    3249258!: { id: number, value: string } | undefined;
  }

After setting that up, we will add a type that will reference the above class.

Type example

type RestaurantGuestResponse= {
 [g in keyof RestaurantGuest ]: RestaurantGuest [g];
};

This type will be used later when we do the type lookup in conjunction with our mapper function. With the above finished, we can now move on to handling our enums.

Handling enums

We’ll now create our Enum to make dealing with our complex data easier. Since the above 3249258 represents FoodReservation, we will create an enum for it. Now you might be wondering why 3249258 represents FoodReservation. Unfortunately, this is an example of how data can be returned to us from a call. It could be due to the field id established in a CMS such as Contentful, or from another source that we don’t control. This isn’t readable, so we’re creating an Enum for the value.

Enum example

export enum FoodReservation {
  'Yes' = 53425867,
  'No' = 53425868,
}

This will be used later during our type look-up.

Type lookup

Now we can start combining the enum from above with our class type, and a look-up type that we’re about to create. So let's make another type called RestaurantGuestFieldLookup, which will have an id and value.

Look-up type example

export type RestaurantGuestFieldLookup<
 TId extends string | number | null,
 TValue extends string | null
> = {
 id: TId;
 value: TValue;
};

Perfect, now we’ll swap out
3249258?: {id: number, value: string} | undefined;

to be
 3249258?: RestaurantGuestFieldLookup<
   FoodReservation,
   keyof typeof FoodReservation
 >;

We can now move on to creating and using our mapper. In a separate file called mapper.ts, we will create our restaruantGuestMapper function.
export const restaruantGuestMapper = (guest: RestaurantGuestResponse) => {
 if (!guest) {
   return null;
 }

 return {
   id: guest.id,
   name: guest.name,
   phoneNumber: guest.phoneNumber,
   email: guest.email,
   reservation: guest.3249258.id === FoodReservation.Yes,
 };
};

Tada! Thanks to all the work above, we can easily understand and get back the data we need.

Conclusion

Today's article covered setting up a typing class, creating an enum, and type lookup with a mapper for more complex data handling with GraphQL. Hopefully, the class structure was straightforward and helpful, along with the enums and type lookup and formatting examples.

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

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

How to Optimize GraphQL Performance with Redis cover image

How to Optimize GraphQL Performance with Redis

Intro GraphQL is a great way to build backend applications. It simplifies the project’s structure and scales easily, but there are some challenges that come with it. One of the biggest is how to optimize GraphQL at scale. In this post, we will show you how to use Redis as an optimization tool for your GraphQL servers. What is Redis? Redis is an open-source database that can be used with any programming language. It is well-suited for large-scale applications. It’s a key-value store that can be used to store and retrieve data. It is designed to be fast, with no single point of failure. It also provides a high degree of concurrency, which means it's possible for multiple clients to access the same data at the same time. Redis is also lightweight enough that you can run it in the cloud. Why GraphQL? GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools. Why Redis with GraphQL? Redis can be your best friend when it comes to optimizing the performance of GraphQL. First, Redis provides a powerful system that helps you to cache queries and results so that they can be reused. This is crucial because there's no way to predict how often you'll need to run a query on a serverless architecture. Sometimes you use a 3rd party API that is slow, or there are request limits, or even the local databases could take a quite long time to return the data. If you're creating tons of different queries every day, this can add up quickly! Different caching strategies There are two main caching strategies that you can use with GraphQL and Apollo Server: 1. Caching the entire response (use Redis as a cache)__ Caching the whole response is the most straightforward way to cache your GraphQL queries. When you cache the entire response, you're essentially caching the entire query, including all of the fields that are returned by the query. This is a great option if you're only interested in caching the data that are returned by the query, and you don't need to worry about caching any of the other data that is returned by the query, or if you have a repeatable query for different users. Example of in-memory cache: 2. Caching individual fields (use Redis as a data store)__ This is a more proper way to cache your GraphQL queries. It's also a more complex way to cache queries in Apollo Server. When you cache individual fields, you're caching the individual fields that are returned by the query. This is a great option if you're only interested in caching the data that are returned by the query, and you don't need to worry about caching any of the other data that are returned by the query. What not to cache? Redis is not built for large data. If you're storing critical business data, you're going to need a more traditional data solution. If you're looking for a way to store complex data queries, look elsewhere. Redis is designed to be simple and fast, but that doesn't mean it's ready for just about anything. Even if you think your data could grow into a large enough set that it would benefit from relational databases, remember that Redis does not have support for building relational databases from scratch. It has no support for tables or relationships or any other kind of constraints that would be required if your data was stored in a relational database. Conclusion In this post, we showed you how to use Redis as an optimization tool for your GraphQL. We also showed you how to use Redis as a cache and as a data store. We hope you found this post helpful! Also, check out our GraphQL Serverless Contentful starter kit on Starter.dev If you have any questions or comments, please feel free to send them to us by email at hi@thisdot.co. Thanks for reading!...

Leveraging GraphQL Scalars to Enhance Your Schema cover image

Leveraging GraphQL Scalars to Enhance Your Schema

Introduction GraphQL has revolutionized the way developers approach application data and API layers, gaining well-deserved momentum in the tech world. Yet, for all its prowess, there's room for enhancement, especially when it comes to its scalar types. By default, GraphQL offers a limited set of these primitives — Int, Float, String, Boolean, and ID — that underpin every schema. While these types serve most use cases, there are scenarios where they fall short, leading developers to yearn for more specificity in their schemas. Enter graphql-scalars, a library designed to bridge this gap. By supplementing GraphQL with a richer set of scalar types, this tool allows for greater precision and flexibility in data representation. In this post, we'll unpack the potential of enhanced Scalars, delve into the extended capabilities provided by graphql-scalars, and demonstrate its transformative power using an existing starter project. Prepare to redefine the boundaries of what your GraphQL schema can achieve. Benefits of Using Scalars GraphQL hinges on the concept of "types." Scalars, being the foundational units of GraphQL's type system, play a pivotal role. While the default Scalars — Int, Float, String, Boolean, and ID — serve many use cases, there's an evident need for more specialized types in intricate web development scenarios. 1. Precision**: Using default Scalars can sometimes lack specificity. Consider representing a date or time in your application with a String; this might lead to ambiguities in format interpretation and potential inconsistencies. 2. Validation**: Specialized scalar types introduce inherent validation. Instead of using a String for an email or a URL, for example, distinct types ensure the data meets expected formats at the query level itself. 3. Expressiveness**: Advanced Scalars provide clearer intentions. They eliminate ambiguity inherent in generic types, making the schema more transparent and self-explanatory. Acknowledging the limitations of the default Scalars, tools like graphql-scalars have emerged. By broadening the range of available data types, graphql-scalars allows developers to describe their data with greater precision and nuance. Demonstrating Scalars in Action with Our Starter Project To truly grasp the transformative power of enhanced Scalars, seeing them in action is pivotal. For this, we'll leverage a popular starter kit: the Serverless framework with Apollo and Contentful. This kit elegantly blends the efficiency of serverless functions with the power of Apollo's GraphQL and Contentful's content management capabilities. Setting Up the Starter: 1. Initialize the Project: `shell npm create @this-dot/starter -- --kit serverless-framework-apollo-contentful ` 2. When prompted, name your project enhance-with-graphql-scalars`. `shell Welcome to starter.dev! (create-starter) ✔ What is the name of your project? … enhance-with-graphql-scalars > Downloading starter kit... ✔ Done! Next steps: cd enhance-with-graphql-scalars npm install (or pnpm install, yarn, etc) ` 3. For a detailed setup, including integrating with Contentful and deploying your serverless functions, please follow the comprehensive guide provided in the starter kit here. 4. And we add the graphql-scalars` package `shell npm install graphql-scalars ` Enhancing with graphql-scalars: Dive into the technology.typedefs.ts` file, which is the beating heart of our GraphQL type definitions for the project. Initially, these are the definitions we encounter: `javascript export const technologyTypeDefs = gql type Technology { id: ID! displayName: String! description: String url: URL } type Query { "Technology: GET" technology(id: ID!): Technology technologies(offset: Int, limit: Int): [Technology!] } type Mutation { "Technology: create, read and delete operations" createTechnology(displayName: String!, description: String, url: String): Technology updateTechnology(id: ID!, fields: TechnologyUpdateFields): Technology deleteTechnology(id: ID!): ID } input TechnologyUpdateFields { "Mutable fields of a technology entity" displayName: String description: String url: String } ; ` Our enhancement strategy is straightforward: Convert the `url` field from a String to the `URL` scalar type, bolstering field validation to adhere strictly to the URL format. Post-integration of graphql-scalars`, and with our adjustments, the revised type definition emerges as: `javascript export const technologyTypeDefs = gql type Technology { id: ID! displayName: String! description: String url: URL } type Query { "Technology: GET" technology(id: ID!): Technology technologies(offset: Int, limit: Int): [Technology!] } type Mutation { "Technology: create, read and delete operations" createTechnology(displayName: String!, description: String, url: URL): Technology updateTechnology(id: ID!, fields: TechnologyUpdateFields): Technology deleteTechnology(id: ID!): ID } input TechnologyUpdateFields { "Mutable fields of a technology entity" displayName: String description: String url: URL } ; ` To cap it off, we integrate the URL` type definition along with its resolvers (sourced from `graphql-scalars`) in the `schema/index.ts` file: `javascript import { mergeResolvers, mergeTypeDefs } from '@graphql-tools/merge'; import { technologyResolvers, technologyTypeDefs } from './technology'; import { URLResolver, URLTypeDefinition } from 'graphql-scalars'; const graphqlScalars = [URLTypeDefinition]; export const typeDefs = mergeTypeDefs([...graphqlScalars, technologyTypeDefs]); export const resolvers = mergeResolvers([{ URL: URLResolver }, technologyResolvers]); ` This facelift doesn't just refine our GraphQL schema but infuses it with innate validation, acting as a beacon for consistent and accurate data. Testing in the GraphQL Sandbox Time to witness our changes in action within the GraphQL sandbox. Ensure your local server is humming along nicely. Kick off with verifying the list query: ` query { technologies { id displayName url }, } ` Output: `json { "data": { "technologies": [ { "id": "4UXuIqJt75kcaB6idLMz3f", "displayName": "GraphQL", "url": "https://graphql.framework.dev/" }, { "id": "5nOshyir74EmqY4Jtuqk2L", "displayName": "Node.js", "url": "https://nodejs.framework.dev/" }, { "id": "5obCOaxbJql6YBeXmnlb5n", "displayName": "Express", "url": "https://www.npmjs.com/package/express" } ] } } ` Success! Each url` in our dataset adheres to the pristine URL format. Any deviation would've slapped us with a format error. Now, let's court danger. Attempt to update the url` field with a _wonky_ format: ` mutation { updateTechnology(id: "4UXuIqJt75kcaB6idLMz3f", fields: { url: "aFakeURLThatShouldThrowError" }) { id displayName url } } ` As anticipated, the API throws up a validation roadblock: `json { "data": {}, "errors": [ { "message": "Expected value of type \"URL\", found \"aFakeURLThatShouldThrowError\"; Invalid URL", "locations": [ { "line": 18, "column": 65 } ], "extensions": { "code": "GRAPHQLVALIDATION_FAILED", "stacktrace": [ "TypeError [ERRINVALID_URL]: Invalid URL", " at new NodeError (node:internal/errors:399:5)", " at new URL (node:internal/url:560:13)", ... ] } } ] } ` For the final act, re-run the initial query to reassure ourselves that the original dataset remains untarnished. Conclusion Enhancing your GraphQL schemas with custom scalars not only amplifies the robustness of your data structures but also streamlines validation and transformation processes. By setting foundational standards at the schema level, we ensure error-free, consistent, and meaningful data exchanges right from the start. The graphql-scalars` library offers an array of scalars that address common challenges developers face. Beyond the `URL` scalar we explored, consider diving into other commonly used scalars such as: **DateTime**: Represents date and time in the ISO 8601 format. **Email**: Validates strings as email addresses. **PositiveInt**: Ensures integer values are positive. **NonNegativeFloat**: Guarantees float values are non-negative. As a potential next step, consider crafting your own custom scalars tailored to your project's specific requirements. Building a custom scalar not only offers unparalleled flexibility but also provides deeper insights into GraphQL's inner workings and its extensibility. Remember, while GraphQL is inherently powerful, the granular enhancements like scalars truly elevate the data-fetching experience for developers and users alike. Always evaluate your project's needs and lean into enhancements that bring the most value. To richer and more intuitive GraphQL schemas!...

Setting Up a Shopify App and Getting Order Data cover image

Setting Up a Shopify App and Getting Order Data

Today, we are going on an adventure! We’re starting a three-part guide on creating a Shopify app and updating a customer's order with tracking information. For this article, it's assumed that you already have a Shopify store. If you want to skip ahead and look at the code, it can be found here. To start us off, we’ll use the Shopify Create app and then follow it up with retrieving customer orders. Shopify Create app Getting started with the Shopify Create app will be a quick and easy process. We’ll start by navigating to a directory where we want to create our app and run the following `shell yarn create @shopify/app. ` We’ll be greeted by a few different prompts asking for the project name, and building your app selection. Success! Now let's navigate into our new directory and do a yarn dev, and we’ll get a few options. We’ll choose to create it as a new app, add an app name, config name, and select the store we want to use. With that out of the way, we’ll open the preview by pressing the p button. It should automatically open it up in a window and show us an app install screen where we will click the Install app button. This should redirect us to our Remix app template screen Install__ Template__ Perfect! We now have a basic Shopify Create app up and running. Next, we will move on to adding in the ability to retrieve our customer orders. Orders query Alright, it’s customer order time! We’re going to be leaving the template mostly as is. We are going to focus on adding the call and verifying the data comes back. We’ll navigate over to our app/routes/app.index.jsx file and start changing the loader function. Start by removing: `js const { session } = await authenticate.admin(request); return json({ shop: session.shop.replace(".myshopify.com", "") }); ` And replacing it with: `js const { admin } = await authenticate.admin(request); const response = await admin.graphql( query getOrders { fulfillmentOrders (first: 30) { edges { node { requestStatus createdAt updatedAt status fulfillAt fulfillBy fulfillments (first: 10) { edges { node { createdAt deliveredAt displayStatus estimatedDeliveryAt id inTransitAt legacyResourceId name status totalQuantity updatedAt trackingInfo { company number url } } } } order { id name note createdAt displayFulfillmentStatus } assignedLocation { address1 address2 city countryCode name phone province zip } destination { address1 address2 city company countryCode email firstName lastName phone province zip } } } } } ); const responseJson = await response.json(); console.log("responseJson", responseJson); const orders = responseJson?.data?.fulfillmentOrders?.edges?.map( (edge) => edge.node ) || [[]]; return json({ orders: orders, }); ` Next, swap Wrap change { shop } in: `js const { shop } = useLoaderData(); ` With ` Orders ` Follow that up with changing `js useEffect(() => { if (productId) { shopify.toast.show("Product created"); } }, [productId]); ` To `js useEffect(() => { if (orders) { shopify.toast.show("Orders received"); } }, [orders]); ` Then, we’ll remove the View product button that has the old shop variable in it. When you go back and look at your application, you should see the Error: Access denied for fulfillmentOrders field. This is due to scopes that we haven’t updated. To fix this, we’ll head over to our shopify.app.toml file and replace `js scopes = “writeproducts” ` with `toml scopes = "writeproducts,read_orders,read_assigned_fulfillment_orders,read_merchant_managed_fulfillment_orders,read_third_party_fulfillment_orders,write_assigned_fulfillment_orders,write_merchant_managed_fulfillment_orders,write_third_party_fulfillment_orders" ` Here is what you should now have: `toml name = "blog-test" clientid = "" applicationurl = "https://speakers-terminals-informed-machines.trycloudflare.com" embedded = true [accessscopes] Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes scopes = "writeproducts,read_orders,read_assigned_fulfillment_orders,read_merchant_managed_fulfillment_orders,read_third_party_fulfillment_orders,write_assigned_fulfillment_orders,write_merchant_managed_fulfillment_orders,write_third_party_fulfillment_orders" [auth] redirecturls = [ "https://speakers-terminals-informed-machines.trycloudflare.com/auth/callback", "https://speakers-terminals-informed-machines.trycloudflare.com/auth/shopify/callback", "https://speakers-terminals-informed-machines.trycloudflare.com/api/auth/callback" ] [webhooks] apiversion = "2023-07" [pos] embedded = false [build] automaticallyupdate_urls_on_dev = true devstore_url = "" ` We’ll now do another yarn dev` which will tell us that our scopes inside the TOML don’t match the scopes in our Partner Dashboard. To fix this, we simply need to run: `shell yarn shopify app config push ` And then we’ll be prompted to confirm our changes with the difference shown. It will give us a success response, and now we can do another yarn dev to look at our application. Doing so brings us back to our old friend the app install page. Only this time it’s telling us to update the app and redirecting us back to the application page. Huh, seems like we’re getting a new error this time. For some reason, the app is not approved to access the FulfillmentOrder object. No worries, follow the link in that message. It should be https://partners.shopify.com/[partnerId]/apps/[appId]/customerdata Here, we select the Protected customer data and just toggle Store management and save. After this, go down to the Protected customer fields (optional) and do the same thing for Name, Email, Phone, and Address. With that all said and done, we’ll exit the page. After that, go back to our application, and refresh it. Tada! It works! We will see a toast notification at the bottom of the screen that says Orders received and in our terminal console, we will see our orders returning. Conclusion That was an exciting start to our three-part adventure. We covered a lot, from setting up a Shopify app to getting our orders back and everything up and running! Next time, we’ll be digging into how to get our fulfillment ids, which will be needed to update a customer's order with tracking information....

Software Team Leadership: Risk Taking & Decision Making with David Cramer, Co-Founder & CTO at Sentry cover image

Software Team Leadership: Risk Taking & Decision Making with David Cramer, Co-Founder & CTO at Sentry

In this episode of the engineering leadership series, Rob Ocel interviews David Cramer, co-founder and CTO of Sentry, delving into the importance of decision-making, risk-taking, and the challenges faced in the software engineering industry. David emphasizes the significance of having conviction and being willing to make decisions, even if they turn out to be wrong. He shares his experience of attending a CEO event, where he discovered that decision-making and conflict resolution are struggles even for successful individuals. David highlights the importance of making decisions quickly and accepting the associated risks, rather than attempting to pursue multiple options simultaneously. He believes that being decisive is crucial in the fast-paced software engineering industry. This approach allows for faster progress and adaptation, even if it means occasionally making mistakes along the way. The success of Sentry is attributed to a combination of factors, including market opportunity and the team's principles and conviction. David acknowledges that bold ideas often carry a higher risk of failure, but if they do succeed, the outcome can be incredibly significant. This mindset has contributed to Sentry’s achievements in the industry. The interview also touches on the challenges of developing and defending opinions in the software engineering field. David acknowledges that it can be difficult to navigate differing viewpoints and conflicting ideas. However, he emphasizes the importance of standing by one's convictions and being open to constructive criticism and feedback. Throughout the conversation, David emphasizes the need for engineering leaders to be decisive and take calculated risks. He encourages leaders to trust their instincts and make decisions promptly, even if they are uncertain about the outcome. This approach fosters a culture of innovation and progress within engineering teams. The episode provides valuable insights into the decision-making process and the challenges faced by engineering leaders. It highlights the importance of conviction, risk-taking, and the ability to make decisions quickly in the software engineering industry. David's experiences and perspectives offer valuable lessons for aspiring engineering leaders looking to navigate the complexities of the field....