Hypi local development support with Hypi CLI

Getting started

Hi all, today we’re excited to announce the availability of the Hypi CLI!

The current Hypi process is new to many developers, it’s not often you can click a button to design and launch your entire backend service.
Whilst we think this is definitely a win for use of use and flexibility, we also know it is not quite what most developers are used to.

Based on feedback from the community user groups one of the most requested features has been a way to work with Hypi locally.
Second was a way to have some of the setup and boiler plate for getting Hypi into a project made easier.

Today, we’re happy to say that both of these are now possible. The Hypi CLI introduces a local development process that allows you to define your backend’s schema and GraphQL queries in your frontend project.
An added bonus of this new workflow is that you can now version control your app’s schema and queries just as you version control the rest of your code!

To get started, you can follow the latest documentation for the CLI in our docs at https://docs.hypi.app/docs/hypi-cli-intro.

As a TLDR; you can get the new CLI by running npm install -g @hypi/cli. If you don’t have npm installed you can download platform specific binaries for Windows, Mac and Linux by getting the latest version from the release page.

Using the CLI

The CLI requires you to authenticate, so first thing’s first - run hypi login.
You can either login with your Hypi email and password, or you can pass the -d argument to login with a namespace and token.

  1. Email password
Enter your email and password
? Email? youremail@domain.com
? Password? [hidden]
Logged in successfully
  1. Organisation namespace and token from Hypi.Tink
hypi login -d
Enter domain and token
? Domain?  latest.store.hypi.01f2ga50p2mzkmyqse17gd2bae.hypi.app
? Token?  <Auth-Token>
Logged in successfully

Once you’ve logged you can now initialise Hypi in your project. In today’s release we have support for Angular, React and Flutter.
You can use Hypi with other frameworks but today’s CLI version works best with these 3. We’ll be adding support for Gatsby, NextJS and many more shortly. If you have a specific framework you want to see support added for then let us know in the comments.

Workflow and process

The workflow for all of the frameworks is largely the same.

  1. Initialise Hypi in your project by running hypi init - this will ask you a series of questions
  2. Sync your local setup with Hypi by running hypi sync <framework> where framework is currently flutter, reactjs or angular

This does a number of things for you. First of all, hypi init will create a .hypi folder in your project.
This folder has a number of files

  1. app.yaml - this is a YAML definition of your app in Hypi that your local changes will be synced to. The details are collected during hypi init
  2. instance.yaml - this is a YAML definition of your instance in Hypi that your changes will be synced from. The details are colelcted during hypi init
  3. schema.graphql - this is a GraphQL schema definition, it is the same as if you used the Hypi table builder or entered plain GraphQL SDL in the Hypi user interface. Locally in your project you can add new GraphQL types in this file and later when you run hypi sync this file is uploaded to Hypi. NOTE: this local file will replace what you have in Hypi, it does not try to merge but instead is a replace operation. Hypi will validate the schema anyway and ensure it only accepts compatible changes but its worth remembering that if you’re making changes in the Hypi UI and in your local environment that these can conflict.

You will also need your GraphQL query definitions. Put these into src/graphql. e.g. src/graphql/myQuery.graphql.
Once you’ve defined your schema and some queries, you can run hypi sync <framework>.

The sync will generate code for you - these will end up in src\generated. For example, in angular if you had a Product type in your schema and some queries for working with product, you can import the generated code definitions like this:

import { ProductsQueryService, UpsertMutationService } from '../../generated/graphql';
These can then be injected into your component’s constructor. The code snippets below are examples that show how the generated code can be used.
You can see framework specific examples in the documentation at:

  1. Flutter - https://docs.hypi.app/docs/hypi-cli-flutter
  2. React - https://docs.hypi.app/docs/hypi-cli-react
  3. Angular - https://docs.hypi.app/docs/hypi-cli-angular

We will be working to provide more examples on these shortly. Below are usage snippets from the docs repeated here for your convenience and we’ll share some subsequent tutorials here on the community.

In the mean time, feel free to ask questions. We hope this helps make things even easier for you, saving you even more time in your app development.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { ProductsQueryService, UpsertMutationService } from '../../generated/graphql';

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css']
})

export class ProductsComponent implements OnInit {

  loading!: boolean;

  products!: Observable<any>;
  productForm = new FormGroup({
    title: new FormControl(''),
    description: new FormControl(''),
    price: new FormControl(0),
  });
  constructor(private productsQueryService: ProductsQueryService,
    private upsertMutationService: UpsertMutationService) { }

  ngOnInit(): void {
   this.getProducts()
  }

  getProducts(){
    this.products = this.productsQueryService
    .watch({ arcql: '*' }, { fetchPolicy: 'network-only' })
    .valueChanges.pipe(map(result => result.data.find.edges));
  }

  onSubmit() {
    console.warn('hi');
    this.upsertMutationService.mutate({
      values: {
        Product: [
          {
            title: this.productForm.get('title')?.value,
            description: this.productForm.get('description')?.value,
            price: this.productForm.get('price')?.value,
          }
        ]
      }
    }).subscribe(() => {
      this.getProducts()
    });
  }

}

A similar thing is possible for React and flutter

import React, { useState } from 'react';
import { useProductsQuery, useUpsertMutation } from '../../generated/graphql'

const ProductList = (props) => {
  const [newProduct, setNewProduct] = useState(false)
  const [productForm, setProductForm] = useState({
    title: '',
    description: ''
  })

  const { loading, error, data } = useProductsQuery({
    variables: { arcql: '*' },
  });
  
  const [upsertMutation, { upsertData, upsertLoading, upsertError }] = useUpsertMutation()

  const onNewProductHandler = (event) => {
    event.preventDefault()
    setNewProduct(true)
  }

  const inputChangedHandler = (event) => {
    setProductForm({
      ...productForm, [event.target.name]: event.target.value
    })
  }
  const submitProductHandler = (event) => {
    console.log('submit')
    event.preventDefault()
    
      upsertMutation({
      variables: {
        values: {
          Product: {
            title: productForm.title,
            description: productForm.description
          }
        }
      }
    })
  }

  if (loading) return <p>loading...</p>;
  if (error) return <p>{error}</p>;

  if (upsertLoading) return <p>loading...</p>;
  if (upsertError) return <p>{error}</p>;
  if (upsertData) return <p>{upsertData}</p>;

  let noProductsOutput = null
  if (data.find.edges.length === 0) {
    noProductsOutput = (
      <div>
        <p>No products found</p>
      </div>
    )
  }
  let addProductOutput = null;
  if (newProduct) {
    addProductOutput = (
      <form onSubmit={submitProductHandler}>
        <label htmlFor="title">Title</label>
        <input
          className="Input"
          type="text"
          id="title"
          name="title"
          value={productForm.title}
          onChange={(event) => inputChangedHandler(event, 'title')}
          placeholder="Title.." />

        <label htmlFor="description">Description</label>
        <input
          className="Input"
          type="text"
          id="description"
          name="description"
          value={productForm.description}
          onChange={inputChangedHandler}
          placeholder="Description.." />

        <button className="Button" > Submit</button>

      </form>
    )
  } else {
    addProductOutput = (
      <button className="Button" onClick={onNewProductHandler}> New Product</button>
    )
  }

  const productsOutput = data.find.edges.map(product => {
    return (
      <div>
        <h1>{product.node.title}</h1>
        <p>{product.node.description}</p>
      </div>
    )
  })

  return (
    <div>
      {noProductsOutput}
      {productsOutput}
      {addProductOutput}
    </div>
  )
};
export default ProductList;