Friday, 9 October 2020

Generating a Schema for GraphQL

When writing the schema that you just defined elsewhere gets a little old....

I have been working on a graphql instance where the main data source is, currently at least, a mongo database. As you may have seen in my last blog post about defining graphql schema's, I was able to include a more useful set of scalar types to make my graph API easier to consume.

I did this using the merge tools in the graphql-tools library. Well, I found another use for those tools in my next TypeScript/Mongo/GraphQL adventure!

First of all I should point out that the process for using graphql-compose-mongoose is well documented in the readme of the library. To summarise, once a mongoose model has been defined it is necessary to define a detailed schema definition and resolvers for graphql. The schema definition and associated resolvers for graphql are a lot of work to write and maintain manually. Here is where this library steps in and helps you generate the types, input types, enumerations and resolvers. It really is quite the life-saver!

I was curious however, as to whether I could include it on top of the schema that I had already defined (see previous blog post). After googling around for a way to merge schemas I found out that it was available from the graphql-tools library that I was already using. So I was able to really easily.

import gqlTools from 'graphql-tools';
import gqlCompose from 'graphql-compose';
import monCompose from 'graphql-compose-mongoose';
const competitionTC = monCompose.composeMongoose(customModel, {});

gqlCompose.schemaComposer.Query.addFields({
	competitions: competitionTC.mongooseResolvers.findMany(),
}); const graphqlSchemaFromMongoose = gqlCompose.schemaComposer.buildSchema(); const existingSchema = gqlTools.makeExecutableSchema({ alreadyExistingTypeDefs, alreadyExistingResolvers, }); const allSchemas = gqlTools.mergeSchemas({ schemas: [ existingSchema, graphqlSchemaFromMongoose ] });

This code works beautifully, although it almost feels like it shouldn't. There is a lot going on and I found myself wondering whether it was all necessary. 

In the graphql UI the competitionTC now has a much more complete filter than the one I had before and skip, limit and sort also work great.


But, the code is messy 😉. Do I really need those extra scalar types?

Something went wrong  Error: Unknown type "Date".

OK then, turns out this is the best solution for now! It produces really complete GraphQL queries and mutations with very little effort.

Resources and references:

The ultimate guide to schema stitching in GraphQL

graphql-compose-mongoose

Tuesday, 29 September 2020

Defining a GraphQL Schema in TypeScript

Basic Example

In the documentation you will see something like this:

  import graphQL from 'graphql';
  
  // Construct a schema using GraphQL schema language
  export default graphQL.buildSchema(`
    type Customer {
      dob: String
    }
  `);
This is great for getting familiar with graphQL initially but what happens when I want to use a type that is not native? For instance, graphQL, out of the box, has only 4 "scalar types" - String, Int, Float and Boolean. 

So what if I want to deal with a date from my data source? Well there are a few things to know: regardless of the storage format graphQL will change it to a "Long Seconds" number (actually the number of millisecond's elapsed since 01-01-1970). Sure you can plug that into new Date on the frontend, but it is a potentially unnecessary cost for the browser in terms of performance and would mean that we must rely on the consuming developer to remember to tidy up our dates.

Diving in further

Such a limited number of Scalar types in graphQL does seem to invite extension and so we have "graphql-scalars". It does exactly what we want:
{
    "name": "Lewis Kinsella",
    "dob": "1994-09-02"
}

However, further inspection of the docs plus also the common use case test in the code base shows that this library works differently. Instead of using buildSchema we have "makeExecutableSchema" with some typeDefs and resolvers.

import gqlTools from 'graphql-tools';

const schema = gqlTools.makeExecutableSchema({
	typeDefs,
	resolvers,
});

So how do I change my existing work to fit with this?

Let's start at the end...

app.use('/graphql', graphqlHTTP({
	schema
});

It looks like resolvers are no longer added as the "root value" argument in the graphqlHTTP object.

Instead we are adding the schema object only. How do we make that?

const schema = gqlTools.makeExecutableSchema({
	typeDefs,
	resolvers,
});

The typeDefs here is mostly what we had before except now we need to merge in our extra scalar functionality.

a) resolvers are merged with the scalar tool resolvers.

const resolvers = gqlTools.mergeResolvers([root, scalarResolvers.resolvers]);
and
b) 
const typeDefs = merge.mergeTypeDefs([customTypeDefs, ...scalarTypeDefs.typeDefs]);

Where customTypeDefs is what we had at the beginning, no need to change it, except to add some extra scalar types!

 import graphQL from 'graphql';
  
  // Construct a schema using GraphQL schema language
  export default graphQL.buildSchema(`
    type Customer {
      dob: Date
    }
  `);
Happy graphQL-ing in TypeScript with Scalar types!

Wednesday, 9 September 2020

Azure Functions with TypeScript

Things to know about using TypeScript with Azure functions:

  1. Make sure that you have node installed using the latest LTS version (Long Term Support). If you need the latest version for other projects try using nvm-windows to manage multiple versions of Node. Install the latest version of nvm using the zip file in assets of the latest release. nvm list and then nvm use. Also nvm install v 64/32bit is pretty nifty.
  2. To run Azure functions manually (particularly useful for timers etc.. anything that is not an Http Trigger). http://localhost:<port>/admin/functions/<FunctionName>. Also for CRON timings see this cheatsheet.
  3. Azure Functions in TypeScript still use CommonJS modules, so while you can use imports and exports in your code the transpiled JS will be using Node style modules. Bear this in mind if you are expecting to be able to use any of your own libraries that compile to anything other than CommonJS. Check your TSConfig.
So is TypeScript support any good? So far it seems good, the only problems are with my own libraries. I have also noticed that you cannot use fat-arrows for Azure Index functions (for those not so familiar - the entry point called by Azure infrastructure).

Friday, 22 May 2020

Jest-diff Issue after upgrading react-scripts


A project I have been working on recently based on CRA (create-react-app) for TypeScript needed a large jump upgrade (3.1.1 to 3.4.0). Finally after working through a lot of other well documented problems I hit this one:

'=' expected. TS1005

The error occuring in the file jest-diff line 1:
import type { DiffOptions } from './types'

This was strange to me because I had not changed anything related to jest or my testing setup. 

Luckily I was able to find this github issue: https://github.com/facebook/jest/issues/9703

The user paulconlin got it spot on- I upgraded to TypeScript 3.8.3 and was able to compile again.