As for me, a linguist enthusiast, also a non-professional developer, this blog as well as the platform of Leaving.Ink wouldn’t be possible without Xata.
Let me quickly introduce Leaving.Ink
first. It is a blogging platform created by me, mainly for myself, some colleagues, and every Web user that doesn’t know much about programming but has something they would like to write. It’s not even for profit; the domain of this blog, the showcase of this platform, “life.leaving.ink”, imitates “life imitating art” by Oscar Wilde.
I run the platform myself. Vercel is its deployment provider. Clerk implements the authentication layer. And most crucially, Xata stores all of our data.
Today, after the launch week of Xata, I’d like to talk about some mechanics of the platform by explaining how Xata is used as our database, and especially focusing on some enhancements I’ve made to this platform with the new Xata features released during the launch week.
We had four tables in our database: settings
, inks
, tags
and comments
, each with its own purpose. Let’s take a look at tags
.
A screenshot taken from the Schema page enabling splendid clarity
tag
, desc
and author
thereof store necessary information about a tag, and auto-generated xata
fields store useful metadata. tag
, desc
are texts, while author
field also includes related information like the user’s subdomain for querying. Through the link
data type of Xata, it’s easily implemented in the form of a link to the record of the specific user who has created the tag.
async function getData(tag: string) {
// so easy to query across tables
const client = getXataClient()
const subdomain = getSubdomain()
const tag = await client
.db
.tags
.select(['desc'])
.getFirst({
filter: {
tag: tag,
+ author: { subdomain }
}
})
// …
}
If we drew a diagram of the relationship between different tables, it could be as follows:
Why doesn’t
comments
have fields linked tosettings
? Because some commenters haven’t necessarily created theirsettings
records, and also no such querying is needed.
You may have noticed that I used the past tense earlier when mentioning the four tables, and that was because now we have five.
Reduce the number of services you manage and securely attach files — images, documents, binaries, and more — directly to a database record.[1]
Yes, Xata now supports files, which perfectly solves the hassle of having to use an external cloud service in order to store images.
All we need to have is:
'use server'
import { auth } from '@clerk/nextjs'
import { getXataClient } from './xata'
export default async function uploadImage(base64: string, type: string, name: string) {
const client = getXataClient()
const record = await client.db.images.create({
author: auth().userId,
image: {
enablePublicUrl: true,
base64Content: base64,
mediaType: type,
name: name.replace(/[^\x00-\x7F]/g, '')
}
})
return record.image!.url
}
Yes, it’s a server action of the Next.js framework, and that greatly reduces all the server-side rendering and data-fetching efforts needed.
And then we can use uploadImage()
everywhere.
It’s been integrated into our Markdown editor.
<MdEditor
renderHTML={md => <Markdown md={md}></Markdown>}
onImageUpload={async (image: File) => {
const tokens = (await toBase64(image)).split(';base64,')
const url = await uploadImage(tokens[1], tokens[0].split(':')[1], image.name)
return url
}}
></MdEditor>
The real power of the Xata file
data type is far more than that:
- Fast retrieval: Lightning fast file retrieval with an integrated CDN for low latency access.
- Secure by default: Control access through your database roles and URL access levels.
- On-the-fly transformation: Peform resize, crop, rotate, and many other transformations in-code through the SDKs.
- Built-in search: Files are storage directly on records in your database, making it easy to search and filter your files.
So we have five tables with the relationship of:
By far, you should see three navigation tabs on the console:
Under different tabs (Inks
, Tags
and Images
) you can find the respective items to edit.
These three navigation tabs are present throughout the administration console, which should be placed in the layout.tsx
as to Next.js. Here’s the code used before:
async function getData() {
const client = getXataClient()
const { userId } = auth()
const inks = await client
.db
.inks
.select(['id', 'title'])
.filter('author.id', userId)
.sort('xata.updatedAt', 'desc')
.getAll()
const tags = await client
.db
.tags
.select(['tag', 'id'])
.filter('author.id', userId)
.getAll()
return { inks, tags }
}
Two requests are made separately for inks
and tags
, which would be even more inefficient if we add another one for images
.
Xata Launch Week gives the solution: column expressions.
You can navigate the link in the opposite direction, from the target table to the source table.
…
The arrow (<-
) token indicates we are using a backwards relationship.[2]
async function getData() {
const client = getXataClient()
const { userId } = auth()
const data = await client
.db
.settings
.select([{
'name': '<-inks.author',
'as': 'inks',
'columns': ['title'],
}, {
'name': '<-tags.author',
'as': 'tags',
'columns': ['tag'],
}, {
'name': '<-images.author',
'as': 'images',
'columns': ['image.name'],
}])
.filter('id', userId)
.getAll()
return {
inks: data[0].inks?.records ?? [],
tags: data[0].tags?.records ?? [],
images: data[0].images?.records ?? [],
}
}
columns
is no longer restricted to a list of column names and accepts expressions of backwards relationships. Here’s an example:
{
'name': '<-inks.author',
'as': 'inks',
'columns': ['title'],
}
name
indicates from which a link
field that meets the requirement should point. Imagine <-table.link
as a condition of “if the link
of record
in table
points to the selected_record
, i.e. selected_record
<-
table.link
, then include record
”. as
indicates the name of the field in which the records will be included. columns
are the columns to return in the target record.
The one-to-many relationship in this single query, therefore, is converse:
That’s it.
At times, I find that reality has so much potential that there is nothing that cannot be more convenient or cannot be realised. n
requests can be reduced to 1
; built-in file attachment can be implemented; things can be created from zero, built to one, and optimised and enhanced and pushed to infinity. Sometimes a release is what we have expected for a long time; sometimes a release is what we haven’t expected for long but find we indeed need.
What is even more amazing is that we, the non-professional developers, can opt in and build together. The infrastructure is no longer a must for consideration. The backend and frontend can finally be unified. New concepts like decentralisation empower more people to get involved. Artificial intelligence’s on the rise, new products are unveiled, and innovations carry on. The dream of modern Web development, the dream of even wider participation, the dream of a Web belonging to everybody, is closer and closer.
A dream doesn’t come closer itself; some build the ladder for others to climb.
As a modest user, I appreciate that.
Xata - File Attachments ↩︎
Xata - Data model ↩︎