Writing another ORM for Hasura with a codegen
Tags: hasura, graphql, javascript, orm
Published: 2020-05-28 21:58:38

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/yodelq3o2j7h987xb8ep.jpg)
Hello, world. My first post, also a first post in English.

# A way of samurai from copy-paster to a library builder
There is nothing bad not to use any library for Graphql codegen. Graphql is simple by itself. Just import something like `apollo` and you good to go.
But time goes, and you came to an idea that you write too often the same field's definition 
```graphql
query {
  user {
    id
    name
  }
}

mutation {
  insert_user(objects: {}) {
    returning {
      id
      name
    }
  }
}
```

for many queries and export them as Fragments to `graphql` folder:
```javascript
export const userFragment = `
  fragment userFragment on user {
    id
    name
  }
`
```

Then you create 20 tables and get annoyed to write tons of similar text every query/mutation/subscription, where the only table_name is changing, and you come with an idea to autogenerate functions for all these operations.

# What we have
- [ahrnee/graphql-codegen-hasura](https://github.com/ahrnee/graphql-codegen-hasura) this creates everything from Fragments you need to write by yourself
- [timeshift92/hasura-orm](https://github.com/timeshift92/hasura-orm) this is less popular repo and also provides another way to query Graphql with method chaining to generate code
- [mrspartak/hasura-om](https://github.com/mrspartak/hasura-om) and of course my stuff
I was amused that there is so little tooling for this repeatable task.

# Approach
First of all, I saw only the `timeshift92/hasura-orm` library, because I searched for ORM only, and the first one was not caught on a search page. This library didn't fit my needs.
At that time, we had our code split up with exporting Fragments and "base" queries
```javascript
export const GET_USER = gql`
  ${USER_PUBLIC_FRAGMENT}
  query GET_USER($limit: Int, $offset: Int, $where: user_bool_exp, $order_by: [user_order_by!]) {
    user(where: $where, limit: $limit, offset: $offset, order_by: $order_by) {
      ...userPublicFields
    }
  }
`;
```
As I mentioned above, this is just stupid copy-pasting stuff for all tables. Also, this a simple 1 table request. We came to a new task to make a transaction request between microservices. Yes, there is a way to solve this just by architecture, but this was a point, I got that we need a convenient library for that.

# What library should do
First of all, this module is for backend, so it will have full access to Hasura (yes, this is bad also, but it is elementary to fix).
- Autogenerating code for queries/mutations/subscriptions
- Have request/ws apps preinstalled in the module
- Autogenerate base fragments

And that's it. The last task was easy to solve with Hasura's `/query` endpoint, where you can just make a couple of SQL queries to Postgres and get all table names, table fields and also primary keys.

# The result
I'm not happy with the result, because the library seemed easy on the first look, but then got ugly quickly. The reason is simple, and it is tough to maintain architecture for the tasks you don't know yet. One of the tasks was arguments for nested fields.
But lib is here and working! So take a look at it:
```
npm i hasura-om
```

```javascript
const { Hasura } = require('hasura-om')

const orm = new Hasura({
    graphqlUrl: 'your hasura endpoint',
    adminSecret: 'your hasura admin secret'
})
//this method will get all info from DB and generate everything for you
await om.generateTablesFromAPI()
```
But of course, you can do everything manually
```javascript
const { Hasura } = require('hasura-om')
const orm = new Hasura({
    graphqlUrl: '',
    adminSecret: ''
})

orm.createTable({ name: 'user' })
    .createField({ name: 'id', 'isPrimary': true })
    .createField({ name: 'name' })
    .generateBaseFragments()
```

Assuming that we have generated everything we need, now we can query like a pro
```javascript
let [err, users] = await orm.query({
  user: {
    where: { last_seen: { _gt: moment().modify(-5, 'minutes').format() } }
  }
})
//users = [{ ...allUserTableFields }]

let isLiveCondition = { 
  last_seen: { _gt: moment().modify(-5, 'minutes').format() } 
}
let [err, userinfo] = await orm.query({
  user: {
    select: {
      where: isLiveCondition 
    },
    aggregate: {
      where: isLiveCondition,
      count: {}
    }
  }
})
/*
users = {
  select: [{ ...allUserTableFields }],
  aggregate: {
    count: 10
  }
}
*/
```

Let's make a mutation in a transaction
```javascript
var [err, result] = await orm.mutate({
  user: {
    insert: {
      objects: { name: 'Peter', bank_id: 7, money: 100 },
      fragment: 'pk'
    },
  },
  bank: {
    update: {
      where: { id: { _eq: 7 } },
      _inc: { money: -100 },
      fields: ['id', 'money']
    }
  }
}, { getFirst: true })
/* 
result = {
  user: { id: 13 },
  bank: { id: 7, money: 127900 }
}
*/
```

Or we can subscribe to new chat messages
```javascript
let unsubscribe = orm.subscribe({
  chat_message: {
    where: { room_id: { _eq: 10 } },
    limit: 1,
    order_by: { ts: 'desc' }
  }
}, ([err, message]) => {
  console.log(message)
}, { getFirst: true })
```

And for all queries above all you have to do is install module, import and initiate. That's it. All tables/fields/primary keys are generated from a query API. 2 base fragments are also auto-generated: 'base' (all table/view fields), 'pk' (just primary keys). And all you have to do is create new Fragments you need:
```javascript
orm.table('user')
  .createFragment('with_logo_posts', [
    orm.table('user').fragment('base'),
    [
      'logo',
      [
        orm.table('images').fragment('base'),
      ]
    ],
    [
      'posts',
      [
        'id',
        'title',
        'ts'
      ]
    ]
  ])
/* 
this will create such fragment, and you can use it by name in any query
fragment with_logo_fragment_user on user {
  ...baseUserFields
  logo {
    ...baseImagesFields
  }
  posts {
    id
    title
    ts
  }
}
*/
```

# Drawbacks
This is time-consuming. Most of the time was spent on tests + docs + lint because initially it was combined like a Frankenstein from some parts. And currently, it needs a bit of cleaning and refactoring.
Object declaration is a bit messy but easier than write tons of text.
No typescript, sorry. Of course, it will suit this lib very well, but I'm still a noob in this field, so didn't want to spend MORE time on it too.

# A Wish
Please, if you find typos or just stupid sounding text, feel free to write somewhere, so I can improve my elven speech. Also, you are welcome to issues https://github.com/mrspartak/hasura-om/issues 
Also, if this is really helpful somehow, I can write more about building queries and `ES6 tagged template` I used in some places in the lib