In this section, we will explore custom scalars in GraphQL. Scalars are the basic data types in GraphQL, such as Int, Float, String, Boolean, and ID. However, sometimes these built-in scalars are not sufficient for your application's needs. Custom scalars allow you to define your own data types with specific validation and parsing logic.

Key Concepts

  1. Definition: Custom scalars are user-defined data types that extend the basic scalar types provided by GraphQL.
  2. Use Cases: Common use cases include dates, email addresses, URLs, and other domain-specific data types.
  3. Implementation: Custom scalars require defining the scalar type in the schema and providing parsing and serialization logic in the resolvers.

Defining Custom Scalars

Step 1: Define the Scalar in the Schema

First, you need to define the custom scalar type in your GraphQL schema.

# schema.graphql
scalar Date

Step 2: Implement the Scalar in the Resolvers

Next, you need to implement the custom scalar in your resolvers. This involves defining how the scalar should be parsed and serialized.

// resolvers.js
const { GraphQLScalarType, Kind } = require('graphql');
const { GraphQLError } = require('graphql/error');

const DateScalar = new GraphQLScalarType({
  name: 'Date',
  description: 'Custom scalar type for dates',
  serialize(value) {
    // Convert outgoing Date to ISO string for JSON
    return value instanceof Date ? value.toISOString() : null;
  },
  parseValue(value) {
    // Convert incoming ISO string to Date
    const date = new Date(value);
    if (isNaN(date.getTime())) {
      throw new GraphQLError('Invalid date format');
    }
    return date;
  },
  parseLiteral(ast) {
    if (ast.kind === Kind.STRING) {
      const date = new Date(ast.value);
      if (isNaN(date.getTime())) {
        throw new GraphQLError('Invalid date format');
      }
      return date;
    }
    return null;
  },
});

module.exports = {
  Date: DateScalar,
};

Step 3: Use the Custom Scalar in Your Schema

Now you can use the custom scalar type in your schema definitions.

# schema.graphql
type Event {
  id: ID!
  name: String!
  date: Date!
}

type Query {
  events: [Event!]!
}

Practical Example

Let's create a simple GraphQL server that uses the custom Date scalar.

Setting Up the Server

  1. Install Dependencies:

    npm install express express-graphql graphql
    
  2. Create the Schema and Resolvers:

    // schema.js
    const { buildSchema } = require('graphql');
    const { Date } = require('./resolvers');
    
    const schema = buildSchema(`
      scalar Date
    
      type Event {
        id: ID!
        name: String!
        date: Date!
      }
    
      type Query {
        events: [Event!]!
      }
    `);
    
    const root = {
      Date,
      events: () => [
        { id: '1', name: 'GraphQL Meetup', date: new Date() },
      ],
    };
    
    module.exports = { schema, root };
    
  3. Create the Server:

    // server.js
    const express = require('express');
    const { graphqlHTTP } = require('express-graphql');
    const { schema, root } = require('./schema');
    
    const app = express();
    
    app.use('/graphql', graphqlHTTP({
      schema,
      rootValue: root,
      graphiql: true,
    }));
    
    app.listen(4000, () => {
      console.log('Running a GraphQL API server at http://localhost:4000/graphql');
    });
    
  4. Run the Server:

    node server.js
    

Querying the Custom Scalar

You can now query the custom Date scalar using GraphiQL or any other GraphQL client.

query {
  events {
    id
    name
    date
  }
}

Exercises

Exercise 1: Create a Custom Scalar for Email

  1. Define the Scalar:

    scalar Email
    
  2. Implement the Scalar:

    const EmailScalar = new GraphQLScalarType({
      name: 'Email',
      description: 'Custom scalar type for email addresses',
      serialize(value) {
        return value;
      },
      parseValue(value) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(value)) {
          throw new GraphQLError('Invalid email format');
        }
        return value;
      },
      parseLiteral(ast) {
        if (ast.kind === Kind.STRING) {
          const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
          if (!emailRegex.test(ast.value)) {
            throw new GraphQLError('Invalid email format');
          }
          return ast.value;
        }
        return null;
      },
    });
    
    module.exports = {
      Email: EmailScalar,
    };
    
  3. Use the Scalar in the Schema:

    type User {
      id: ID!
      email: Email!
    }
    
    type Query {
      users: [User!]!
    }
    

Solution

  1. Query:
    query {
      users {
        id
        email
      }
    }
    

Conclusion

Custom scalars in GraphQL allow you to extend the basic scalar types to fit your application's specific needs. By defining custom scalars, you can ensure that your data is validated and parsed correctly, providing a more robust and flexible API. In this section, we covered the basics of defining and implementing custom scalars, and provided practical examples and exercises to reinforce the concepts. In the next module, we will delve into more advanced schema design techniques.

© Copyright 2024. All rights reserved