New year, new blog (or how I created this blog for 2023)

Published at:Published at:Updated at:

New year, new blog! After delaying the publication of my blog for a long time, I finally finished developing it using Next.js, PocketBase, and Mantine. Want to know why I chose these tools? Then, keep reading here with me.

I’ve been creating blogs for a long time (since 2007). I started with Blogger, but then I migrated to WordPress. And that’s when I started to be interested in Linux and programming. I spent a lot of time creating themes, customizing plugins, reading documentation, and translating themes and plugins for WordPress. And, although WordPress is an excellent CMS for those who just want to publish a website as quickly as possible, this time I wanted something more personalized, containing all the features I would like to have and nothing more. From there, I started researching.

I tried several CMSs (Directus, KeystoneJS, Strapi and Cockpit), but what I found most simple to meet my need was PocketBase, mainly because I intended to self-host my solution. The other CMSs are great, but when you’re a team of one, you have to choose the right tools. And what’s easier for one person to manage than an SQLite database? PocketBase already exposes database updates in real time with SSE, provides authentication and file management (with integration with S3), SDK for JavaScript and Flutter, and can even be used as a framework. All this within a small binary written in Go (if you want to know more about PocketBase, read the documentation and watch this video from FireShip, where he shows how to create a real-time chat system with PocketBase). And finally, in order to have real-time backups of my SQLite database and send them to S3, I use Litestream. Well, having made the choice for the backend, let’s move on to the frontend.

I tried Astro (which is excellent!) and Remix, but I ended up choosing Next.js, mainly because of the Vercel image generation library, which I use to generate images of the post, like this one:

The job that's never started as takes longest to finish

And here we come to the choice of what I would use to create the styles of the blog. In recent years, I styled React applications with CSS Modules, Styled Components, Stitches, Tailwind and Chakra UI. I even stated to create a Design System with Stitches and Tailwind, but create an entire Design System all by myself would take a long time, so, I decided to take the shorter route.

I have tried a few libraries until I found Mantine, which is an excellent library packaged with everything I wanted to use. From there, the work consisted of implementing the blog with the basic initial features:

  • Incremental Static Regeneration of posts;
  • Form validation with Zod;
  • Nested comment system with anti-spam verification provided by Akismet;
  • Display of commentator avatars with Gravatar;
  • SVG Favicon with light/dark mode;
  • I18n (Portuguese and English).

With all that ready, I changed the canonical URLs of my articles on Dev.to to point to the new URLs and finally published my blog.

Of course, if you’re reading this on my blog now, you’ll see that an important feature is still missing: search. I’ll be studying possible solutions for this in the coming days, but I’ll already let you know that you can preview the functionality by pressing the / key on any page.

Happy 2023, everyone 🎉.

Introduction to GraphQL

Published at:Published at:Updated at:

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data, developed by Facebook in 2012 and open sourced in 2015. The goal was to create a query language that allowed fine-grained control over the needed data a client can request to an API server.

A GraphQL service is created by defining types and fields to those types in a schema. A commom way to defining the schema of your GraphQL service is through the GraphQL Schema Definition Language (SDL).

In this article, I’ll show how to create a GraphQL schema compliant with the Relay GraphQL Server specification.

Defining your schema in GraphQL

A GraphQL schema should informe the users about all the types and objects that can be queried and mutated on the graph. GraphQL even provides a feature to query metadata about those types and objects, which can be used to document the GraphQL.

Let’s define a simple schema using GraphQL SDL (Schema Definition Language):

"""
Serialize and validate JavaScript dates.
"""
scalar Date

"""
Node interface
"""
interface Node {
  id: ID!
}

"""
Priority level
"""
enum Priority {
  LOW
  MEDIUM
  HIGH
}

"""
A task that the user should complete
"""
type Task implements Node {
  "Task ID"
  id ID! # ID is a GraphQL special type for IDs.
  "Task title"
  title String!
  "Task creation date"
  created: Date!
  "Task modification date"
  modified: Date
  priority: Priority!
}

"""
Needed input data to add a new task.
"""
input AddTaskInput {
  title: String!
  priority: Priority
}

type Query {
  "Get a task by ID"
  task(id: ID!): Task
}

type Mutation {
  addTask(input: AddTaskInput): Task
}
  1. First we define a custom Date scalar that should validate and serialize Date objects;
  2. We define a Node interface. I’ll explain more on why I’m defining this interface the next topic;
  3. We define an enumeration type to define the valid priority status of a task;
  4. We create our Task type with all the field it should contain. Notice that all field with the exclamation mark at the end are obligatory;
  5. We add an input called AddTaskInput that defines the obligatory data to add a new Task;
  6. In the Query type (which is a GraphQL reserved type), we define what queries are available from our root object;
  7. In the Mutation type (which is a GraphQL reserved type), we define which operations to alter our data are available. Such operations are called mutations.

Notice that, in GraphQL, comments between quotes serve as documentation (it’ll be parsed and displayed in your GraphiQL web documentation interface), while the comments that start with # are ignored.

Querying your data in GraphQL

Tipically, you’d query a GraphQL server like this:

{
  task(id: "2") {
    title
  }
}

Which would return the following, in JSON format:

{
  "data": {
    "task": {
      "title": "Write GraphQL tutorial"
    }
  }
}

In the query above, we started with a special “root” object, from where we select the task field with the id equals to 2 Then, we select the title field from the task object. But, if no task has an id equals to 2? In this case, our response would be:

{
  "data": {
    "task": null
  }
}

Or, in case of a error, we would receive this response:

{
  "data": {
    "task": null
  },
  "errors": [
    {
      "message": "Internal server error."
    }
  ]
}

You may want rename a field before using your data. Well, you can create your aliases just like this:

{
  todo: task(id: "2") {
    name: title
  }
}

And that would be the return:

{
  "data": {
    "todo": {
      "name": "Write GraphQL tutorial"
    }
  }
}

GraphQL also provides the feature of create query fragments and setting up directives to query your data. I’ll need to add more complexity to our current schema in order to explain that, so, for while, let’s move to the next topic.

The Relay GraphQL Server specification

Despite you may not want to use Relay (or even React) to consume your GraphQL data, their specification is very useful and provides a common ground of what developers should expect from a GraphQL server.

Remember that Node interface we defined above? Its purpose is to provide a global object identification for all the GraphQL nodes in our server. Therefore, a GraphQL client can handle re-fetching and caching in a more standardized way. Notice that each ID must be globally unique on your application.

As the Node interface will be used for all objects in our server, GraphQL provides a reusable unit called fragment. Now, let’s add a new way the query nodes on our schema:

# ...

type Query {
  "Get a node by ID"
  node(id: ID!): Node
}

# ...

Notice that the task query was removed, as it is no more needed. And now, we will re-do our query using a fragment:

# We name the query and pass a variable
# to improve the development experience.
query getTask(id: ID!) {
  node(id: $id) {
    ...taskFields
  }
}

fragment taskFields on Task {
  title
}

And now, we will change our schema to comply with the Relay GraphQL Server specification. Take some time to read the comments in order to understand what is being done here.

"""
Serialize and validate JavaScript dates.
"""
scalar Date

"""
Node interface
"""
interface Node {
  id: ID!
}

"""
Priority level
"""
enum Priority {
  LOW
  MEDIUM
  HIGH
}

"""
A task that the user should complete
"""
type Task implements Node {
  "Task ID"
  id ID! # ID is a GraphQL special type for IDs.
  "Task title"
  title String!
  "Task creation date"
  created: Date!
  "Task modification date"
  modified: Date
  priority: Priority!
}

"""
Define an edge of the task,
containing a node and a pagination cursor.
"""
type TaskEdge {
  cursor: String!
  node: Task
}

"""
Define a connection between the
task edges, including the PageInfo
object for pagination info.
"""
type TaskConnection {
  edges: [TaskEdge] # Yes, we use brackets to define arrays in GraphQL
  pageInfo: PageInfo!
}

"""
Provides pagination info
for a cursor-based pagination
"""
type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

"""
Needed input data to add a new task.
"""
input AddTaskInput {
  title: String!
  priority: Priority
}

type Query {
  node(id: ID!): Node
  tasks(
    first: Int, # The amount of tasks requested
    after: String # Cursor to mark the point
  ): TaskConnection
}

type Mutation {
  addTask(input: AddTaskInput): Task
}

At this point, the metaphor of graphs used here should be very clear. Each edge of your graph has a node and a connection of edges has a collection of nodes that can be paginated. Note that, in this specification, is expected that you implement a cursor based pagination, rather than a offset pagination (follow the previous link to have more information about their differences.

And that’s all we need to comply with the Relay GraphQL Server Specification.

In the next article, I’ll implement a GraphQL server using all the concepts that we learned here.

Source:

GraphQL