Docs
Guides
Building With Pylon
Hardcoded Data

Hardcoded Data Setup

The easiest way to get started with Pylon is to use in-memory (hardcoded) data. For this, we will create a simple class that holds the data and performs all the necessary operations on it.

October 11th, 2024 by Jan Emig

Project Structure

After setting up Pylon correctly, you should see a project structure similar to this:

my-pylon/
├── .pylon/
├── src/
│   ├── index.ts
├── package.json
├── tsconfig.json

The directory we are interested in is the src/ directory. This is where we will create our class BookingStore that holds the static data.

Info

Pylon doesn't restrict you in any way when it comes to implementing your logic. You can use classes, functions, or any other way you like.

Create a Data Structure

Since Pylon let's us freely implement our logic, independent of the structure and its location, we can simply create a new file bookingStore.ts directly in the src/ directory:

my-pylon/
├── .pylon/
├── src/
│   ├── bookingStore.ts
│   ├── index.ts
├── package.json
├── tsconfig.json

Before we start implementing the class, it's a good idea to think about what our data structure should look like. Therefore, we will create a simple interface Booking that represents a booking:

interface Booking {
  id: number
  firstname: string
  lastname: string
  totalPrice?: number
  depositpaid: boolean
  bookingdates: {
    checkin: string
    checkout: string
  }
  additionalneeds?: string
}

Each Booking object represents a booking of a guest, including the guest's name, the total price, if a deposit was paid, the booking dates, and additional needs (e.g. Breakfast).

Implement the Class

Now that we have defined our data structure, we can start implementing the BookingStore class. This class will hold the static data and provide methods to interact with it.

class BookingStore {
  private bookings: Booking[] = [
    {
      id: 1,
      firstname: 'Sally',
      lastname: 'Brown',
      totalPrice: 111,
      depositpaid: true,
      bookingdates: {
        checkin: '2013-02-23',
        checkout: '2014-10-23'
      },
      additionalneeds: 'Breakfast'
    },
    {
      id: 2,
      firstname: 'John',
      lastname: 'Doe',
      totalPrice: 222,
      depositpaid: false,
      bookingdates: {
        checkin: '2013-02-23',
        checkout: '2014-10-23'
      },
      additionalneeds: 'Breakfast'
    },
    {
      id: 3,
      firstname: 'Jane',
      lastname: 'Smith',
      totalPrice: 333,
      depositpaid: true,
      bookingdates: {
        checkin: '2013-02-23',
        checkout: '2014-10-23'
      },
      additionalneeds: 'Breakfast'
    }
  ]
}

Currently, the class only holds the data. In the next step, we will add methods to interact with the data.

Add Methods to Interact with the Data

To interact with the data, we will add methods to the BookingStore class. For this example, we will implement two methods: getBookings and getBookingById.

getBookings(): Booking[] {
    return this.bookings;
}
 
getBookingById(bookingId: number): Booking | null {
    return this.bookings.find((booking) => booking.id === bookingId) ?? null;
}

Note that we accept a bookingId as an argument for the getBookingById method. Pylon recognizes this argument, provides it in the GraphQL interface, and reliably passes it to the method when it is called. This allows us to fetch a specific booking based on the provided ID.

Info

Defining arguments in the method signature will be automatically added to the GraphQL schema and can be used in the respective queries or mutations. Pylon takes care of passing the arguments to the method when it is called and returning the correct data. We only need to assure that enough type safety is given.

Bind this to the Class Methods

Great! We are missing one last step inside our class implementation: Binding this to the class methods so that we can access the class properties inside the methods.

constructor() {
  this.getBookings = this.getBookings.bind(this)
  this.getBookingById = this.getBookingById.bind(this)
}

Now, we can use the BookingStore class to get all bookings or a specific booking by its ID. However, we still need to tell Pylon that we want to use this class in our service.

Important

Don't forget to bind this to any of the class methods that you want to use in your service. Otherwise, you will get a runtime error when calling a class method that uses this.

Register the Class Methods in Pylon

Though we have implemented the BookingStore class, Pylon doesn't know about it yet. To make it available in our service, we first need to export an instance from our newly created class. Since we need to access the same instance for every request, we will export a singleton instance.

const bookingStore = new BookingStore()
 
export default bookingStore

Now, the only thing left to do is to notify Pylon about our new class methods. For this, the index.ts file is the place to go. Currently, the file should look like this:

import {app} from '@getcronit/pylon'
 
export const graphql = {
  Query: {
    hello: () => 'Hello World'
  },
  Mutation: {}
}
 
export default app

Since we dont need the hello resolver anymore, we can remove it and import the bookingStore instance. We can then add the getBookings and getBookingById methods to the Query object:

import {app} from '@getcronit/pylon'
import bookingStore from './lib/bookingStore'
 
export const graphql = {
  Query: {
    bookings: bookingStore.getBookings,
    booking: bookingStore.getBookingById
  },
  Mutation: {}
}
 
export default app

The property names bookings and booking are the names of the queries that we can use in the GraphQL interface. The values are the methods that we want to call when the queries are executed.

And that's it! We have successfully implemented a simple class that holds hardcoded data and provides methods to interact with it. To test the implementation, Pylon provides a built-in GraphQL playground where we can execute our new queries after running bun dev:

Pylon GraphQL Query

How Does It Work?

When we start the service, Pylon automatically creates a GraphQL schema based on the methods we registered in the graphql object. This schema is then used to validate incoming queries and mutations. When a query or mutation is executed, Pylon calls the corresponding method and returns the result to the client.

You can think of Pylon as a kind of “mapper” with regard to GraphQL. When we introduce new functions to Pylon, it checks the signature of the function and builds a GraphQL schema from the TypeScript types. This automatically converts our functions into GraphQL endpoints. This means that we don't have to write GraphQL schemas manually, Pylon does it for us and we can just focus on implementing the functions.

Furthermore, Pylon does not care how we write and build our functions. We can rely on complex classes, whose methods we then introduce into Pylon, or we can rely on simple functions, which we then include in Pylon. We can also rely on a mixture of both. Pylon is very flexible in this respect and allows us to find the solution that suits us best. Pylon only expects us to annotate our code type so that Pylon can generate the correct GraphQL schema.

Modify the Data

Modifying the data works the same way as reading it. Let's say we want to add a new booking to our data store. We can simply add a new method to the BookingStore class:

addBooking(booking: Omit<Booking, "id">): Booking {
    const newBooking = { ...booking, id: this.bookings.length + 1 };
    this.bookings.push(newBooking);
    return newBooking;
}

This method is pretty straightforward. It takes a new booking object, assigns a new ID to it, and adds it to the bookings array. Since we dont want the client to set the ID, we use Omit<Booking, "id"> to exclude the id property from the booking object. You can of course put each type field into a separate parameter if you prefer. We use this approach to keep the method signature simple.

To make the new method available in our service, we need to add it to the index.ts file (dont forget to bind this to the method too):

export const graphql = {
  Query: {
    bookings: bookingStore.getBookings,
    booking: bookingStore.getBookingById
  },
  Mutation: {
    addBooking: bookingStore.addBooking
  }
}

Since this method changes our data, we place it in the Mutation object. Now, we can start using this new mutation. To test it, we can use the built-in GraphQL playground:

Pylon GraphQL Mutation

Perfect! We have successfully implemented a simple class that holds in-memory data and provides methods to interact with it.

You can go ahead and implement more methods to interact with the data, like updating or deleting a booking. The possibilities are endless, and Pylon gives you the freedom to implement your logic the way you like it.

Conclusion

This guide showed how easy it is to get started with Pylon. We created a simple class that holds hardcoded data and provides methods to interact with it. We then registered the class methods in Pylon and tested them using the built-in GraphQL playground.

In the next guide, we will take a look at how we can use external data sources to fetch data in our service.