Earlier this year, in a previous article, I push back on going serverless. Not because I think serverless is bad, but because the current serverless trend involves some practices that I don't think are useful for 95% of the apps that get built out there.
If you want to detour here's the previous article 👇 I'll wait here drinking my 🧉.
My monolith doesn't fit in your serverless
Gabriel Chertok ・ Jul 28 '20
👋 Welcome back! As I was saying, I still think the same way. I still feel we need full stack frameworks instead of 15 specialized tools that you can consume from a front-end, and I understand where this pressure of using the right tool for the job comes from, but sometimes a hammer is good enough.
Hopefully, today I can marry these two worlds. Benefiting from serverless infrastructure while developing with a full-stack framework, as if you were writing Django or Ruby on Rails. Let's explore Blitz.js.
Enter Blitz.js
Blitz.js is a full-stack framework adapted for the serverless era. It carries all the benefits of serverless ready frameworks like Next.js -it's built on top of it- while adopting features like a data layer or a set of reasonable defaults.
Blitz.js is built on top of Next.js supporting most, if not all, Next.js features such as React for the view layer, Server Side Rendering (SSR), Static Site Generation (SSG), and the new Incremental Site Generation (ISG), but I feel the exciting parts are in the differences.
Serverless era?
Currently, full-stack frameworks can't run on platforms like AWS lambda or Vercel. These platforms can support different languages like ruby, Java, or PHP, but the full-stack frameworks' programming model doesn't play nicely with the constraints FaaS exposes.
Blitz.js embraces the FaaS constraints. You have no controllers, but stateless functions that can be executed as a long-running nodejs process or invoked as a lambda function.
Typescript
By default, Blitz.js wants you to use Typescript: you can opt-out, but I wouldn't recommend it. TypeScript is a solid language, and the framework generators and all the internals are written in this language.
Code organization
While Next.js doesn't hold too many opinions, maybe non outside how to do routing, Blitz.js does.
First, it encourages you to group files by functionality and not by role. If you have worked with a full-stack framework before, you know a big part of the framework's responsibility is to make these decisions for you.
├── app
│  ├── components
│  ├── layouts
│  ├── pages
│  │  ├── _app.tsx
│  │  ├── _document.tsx
│  │  └── index.tsx
│  ├── products
│  │  ├── components
│  │  │  └── ProductForm.tsx
│  │  ├── mutations
│  │  │  ├── createProduct.ts
│  │  │  ├── deleteProduct.ts
│  │  │  └── updateProduct.ts
│  │  ├── pages
│  │  │  └── products
│  │  └── queries
│  │  ├── getProduct.ts
│  │  └── getProducts.ts
│  └── queries
│  └── getReferer.ts
...
Routes
Here you see how products
and app
have both a pages
directory. At runtime, all these routes are smashed together.
Queries & mutations
Besides pages, we see other types of files, such as queries and mutations. Let's explain those.
Queries and mutations are what you would expect, a way to query and store data from/to your database. While it's not restricted to the DB layer, it's probably the most common scenario.
Blitz.js uses Prisma 2, a framework to abstract the interactions with the database, and it's used like this:
import db from "db"
type GetCompaniesInput = {
where?: FindManyCompanyArgs["where"]
}
export default async function getCompanies(
{ orderBy = { createdAt: "asc" } }: GetCompaniesInput,
_ = {}
) {
const companies = await db.company.findMany({
orderBy,
})
return companies
}
Queries -and mutations- are not API endpoints, but regular TS functions that you can import from your components and call. This is a novel concept I haven't seen in any other frameworks, called Zero-API.
The idea behind the Zero-API is to allow you to call a function from a React component, while swapping that call at compile time for an API request. This results in a simpler programming model. Importing and calling vs. dealing with endpoints, with the added benefit of TS type checking inputs and results. The framework makes the heavy lift for us at build time.
export const Companies = () => {
const [companies] = useQuery(getCompanies, {})
return (
<>
<h1 className="font-bold text-4xl mb-8">Companies</h1>
{companies.map((company) => {
return <Company key={company.id} {...company} />
})}
</>
)
}
Queries are called from the front-end with a useQuery
hook. For mutations, no hook is needed you can just await
the mutation response. Also, types are carried over from the hook to your variables.
Prisma 2
We talked about Prisma 2 when discussing queries and mutations, but it deserves a bit more explanation. At its core, Prisma is a set of packages that allows you to interact with relational databases using node or TypeScript.
If you choose TypeScript as Blitz does this, you get complete type safety for your models and DB operations, since Prisma will generate not only model types but types for querying and mutating the resource.
The way Prisma works is by having a schema file with a custom DSL. This schema is similar to the one you can find in Rails, but instead of being the result of applying migrations it operates as the source of truth, and migrations are generated from this file.
datasource db {
provider = ["sqlite", "postgres"]
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
// --------------------------------------
model Company {
id Int @default(autoincrement()) @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String
description String
logo String
url String @default("")
hasOffices Boolean
allowsFullRemote Boolean
}
After you run the blitz db migrate
command, Prisma will generate a migration -a snapshot of the actual schema- and a Prisma client. A Prisma client is the package we use to interact with the DB and has the generated types for our schema.
CLI
Most of the things I talked about here can be created though the Blitz CLI. Currently, it has almost everything you need to start working with the framework such as blitz new {PROJECT NAME}
or blitz generate
to generate models, scaffolds pages and more, as well as the blitz db
command to interact with Prisma using the same CLI.
Final words
There are many more things I wish I had covered in this review, such as the new upcoming seed command, the built-in authentication or the recipes.
I will be writing more about Blitz since I'm using it to rebuild remote.uy, so hopefully, I can cover more ground and learn since I'm not an expert on the subject, and the framework is rapidly evolving.
If you liked the framework, give it a try, and join the Slack community where most of the action takes place.
Liked the post? Shout out to Gustavo, Franco, Pepe, and Ruben that helped me edit and refine this article.
Top comments (0)