What changed for full-stack
The concept of full-stack development has been around for many years, but its popularity has surged dramatically since 2022. As a consequence, its meaning has also changed.
Originally a full-stack developer was a guy who was proficient in both front-end and back-end technologies. It’s just two roles in one guy, as neither the technology nor the skill set is the same. Front-end developers mostly speak HTML/CSS/Javascript and build fancy UI, and back-end folks use PHP/Java/C# to implement server-side business logic.
As in many other cases throughout human history, tools are often the main drivers of revolutionary change. Let's take a look at some representative tools in this field.
TypeScript
Thanks to TypeScript, I think it’s for the first time in history that both front-end and back-end developers could and is willing to use the same programming language to work. You might argue that JavaScript has already achieved that during 2009 when Node.js was announced. But I heard lots of backend developers who switched from strongly typed languages like Java or C#, including myself; we are definitely not willing to use Javascript. The main reason, as specified by Hejlsberge, core developer of Typescript, in the recent interview, is that:
they simply could not scale for the large JavaScript apps
And the reason for that is:
- There is no object-oriented programming in JavaScript.
- Without the type system, it’s not possible for you to specify your intent in code which makes the maintenance extremely hard.
- Without the type system, it’s hard to build tooling.
Next.js
Thanks to Next.js, it allows you to write your front-end and back-end code in the same framework. Using a function like getServerSideProps, you actually write the front-end and backend-end code in the same file:
Or, with the newly introduced Server Component, you actually write both front-end and back-end code in the same function:
As the convenience it brings to allow you to write the whole web app in one single framework, it does bring a little burden as sometimes you probably would think: wait a minute, is this code gonna run in the server or in the browser? 😅
tRPC
Thanks to tRPC, it allows you to forget about the network boundary and call the remote function like the local function with end-to-end typesafe:
The “Go to Definition” and “Rename Symbol” also works across the network boundary. So sometimes you really forget you are actually calling the remote procedure.
With the integration with Zod, the backend validation has been taken care of. With the wrapper around React Query, the caching has also been taken care of. So probably the only thing you need to do out of the front-end work is to define and implement the router function.
Take a look at the google trend, and you will see the same leap time as full-stack around 2022.
I bet you are aware of other tools that emerged during that period that changed the mindset of full-stack development. So as the famous “Ship of Theseus” paradox:
if all of the parts of a ship have been replaced over time, is it still the same ship or a new one
I would think of it as a new one because you can see more and more people who don’t have the traditional backend technology and skills and could also build a complete web app which was impossible before.
So how about calling the new fullstack as ZenStack? Think of it as the Zen mode of the fullstack to let you focus on building what matters - the user experience, and less on usability like secure, reliable, scalable things.
Where does the backend complexity go?
Although it’s great to focus on the user experience now, things can’t simply disappear, so where does the backend complexity go?
Let’s take a look at some typical tasks of the backend
Design and implementing database schema
This is usually the first task for a backend developer. This used to involve creating database tables, defining relationships between tables, defining the corresponding entity class in code, etc. In addition, you also need to handle schema migration, which can be both risky and frequent.It requires a good understanding of the database and the SQL language.
Thanks to Prisma, all of the above tasks have been simplified into an intuitive data model, as shown below:
model User {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
email String @unique
name String?
role Role @default(USER)
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
published Boolean @default(false)
title String @db.VarChar(255)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
enum Role {
USER
ADMIN
}
You get auto-generated Typescript type with type-safety, auto-completed query builder, and automated migrations.
Developing API
whether using RESTful or GraphQL, it usually takes a lot of time to design and implement a set of endpoints that can be used to perform CRUD (Create, Read, Update, Delete) operations on data in the database.
Thanks to tRPC, as you have already seen, you can almost forget about this part by only defining the tRPC router. Actually, by using the prisma-trpc-generator, you don’t even need to define the router. It could directly generate it from the Prisma data models.
Implementing user authentication and authorization
Authentication used to be a very challenging part as Modern apps often favor OAuth-based authentication, which delegates the identity verification to a trusted 3rd-party. Explaining how OAuth works is one of my favorite interview questions, but very few interviewees could explain it well.
Thanks to NextAuth, it gives an excellent solution to bring in the complexity of security without the hassle of having to build it yourself. it comes with an extensive list of providers to quickly add OAuth authentication and provides adapters for many databases and ORMs, including Prisma.
Authorization is based on Authentication. it controls "who can take what action to which asset". So essentially, it is the same thing with Access Control. It is even more complex and can be challenging for even experienced developers to get right. Not only because you need to have a great understanding of different access control models, such as role-based access control (RBAC), attribute-based access control (ABAC), and discretionary access control (DAC), but also has the great architecture capability to make it clear and scalable as the logic is dispersed among the code base.
Here comes the ZenStack, but this time is the toolkit we are currently building. Following its vision to let you focus on building what matters, one of our primary goals is to relieve the pain points of access control for you. We are lucky to be able to stand on the shoulders of giants. ZenStack is built on top of Prisma and also has integration with tPRC and NextAuth. So by adopting it, you get all the benefits provided by these tools mentioned above, plus the declarative access policies defined in your model:
Business logic
This is the majority of the work and is what really matters for each business. Even though, now it has been much easier because lots of the general works have been abstracted away. There are more and more SaaS companies that provide out-of-box solutions for vertical functionality like payment, email, customer service, CMS, CRM, e-commerce, etc. All you need to do is to integrate with them.
However, there is no standard to integrate with these 3rd parties. You need to go through the documentation, API, and SDK of each and integrate it into your system. In my opinion, It’s not only time-consuming but also could make the whole system less stable if you don’t do it right. Do you also see this as a pain point for you? Do you want to have a standard and centralized way to see these 3rd party models as part of your own model, like:
// nextauth
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? // @db.Text
access_token String? // @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? // @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
// paypal
model Payment {
id Int @id @default(autoincrement())
orderID String
status String
}
// shopify
model Order{
id Int @id
billing_address Address
cart_token String
checkout_token String
client_details ClientDetail
current_total_price Int
}
If this is what you need or you have any other pain point hope could be solved, welcome to join our discord or discuss it in GitHub. Let’s make a better ZenStack together.
Top comments (10)
I worked for years as a backend engineer, then as a frontend engineer and now I'm doing both with a little bit of mobile development (through
react-native
). I think we must not omit the fact that much of our work as developers has nothing to do with programming language, but with understanding the specifics of platforms we work with.You can't use the same paradigm (say Object Oriented) effectively over the range of
CSS
which is an exception based language (i.e. you define sane and smart defaults and then just specify what is the "difference" for a particular part of your HTML) then you might haveJava
which is OO paradigm, and then you haveSQL
which is a declarative paradigm.Also, we use Prisma and it's all good until you have a real world need where you are better off writing a complex raw SQL query; we also had multiple cases where we had to write very custom SQL as part of the "automated migration" - the abstractions run out juice at one point and then you are either a master of each domain's paradigm or you will run into problems.
I wrote a longer piece as a response.
dev.to/latobibor/full-stack-end-of...
I have a similar work experience to yours, and I understand your point about the potential drawbacks of increasing abstraction in software engineering. In engineering, we often face trade-offs when deciding how much abstraction to use.
While it's possible that some may encounter limitations with abstraction, it can also provide opportunities for more people to deliver value incrementally and improve gradually, much like the Scrum framework. Additionally, individuals may still have the option to work with more raw forms of technology, such as writing raw SQL, if they possess the necessary knowledge. Nevertheless, it's difficult to ignore the efficiency that tools like Prisma can bring to our work, which aligns with the idea that "Everything that can be abstracted will be abstracted".
This is one of the reasons why we developed ZenStack. Our goal is to establish a solid foundation from the beginning so that if any limitations are encountered, they can be gradually addressed without requiring a complete rewrite.
Thanks for your great reply! I wish to emphasize that I do not have anything against abstractions (unless they are quite leaky) and with better and better tools we are actually getting rid of wasted time - i.e. time spent on figuring out basic infrastructure, just like JS bundling, DB connection, etc.
The point I'm making that the domain and platform of each stack must be learned if you are planning to have a mature, scalable application and that every organization must also hire the necessary specialists, or to think of it as different roles required at different stages of a firm: startups need more full-stacks and established companies need really senior specialists.
I completely agree with you. Our goal is to assist startups in rapidly building full stacks and validating their business concepts. We also hope that as they become established companies, senior specialists can expand upon the stack we have provided.
Great to see new projects built on top of Prisma! We're doing something in a similar vein, but actually trying to be fairly opinionated and providing a lot of best practices out-of-the-box to make developers' lives easier.
Currently we're supporting React & Node.js (although other libs/frameworks are also in the plans). Maybe you find it useful for inspiration: wasp-lang.dev/
It's great to see that we share the same goal to make developers' lives easier. Actually I came across your product before, and I must say I am impressed by your courage to pursue such a disruptive innovation. I just make one small step instead. 😂
Anyway, let’s move together, maybe we will have a chance to collaborate some day.
It makes me so sad to read that we are encouraging the use of Javascript for backend development. Backend needs to be done with robust and fast languages. What we do now is we throw a lot of resources at it and don't care. But there are things fundamentally wrong with Javascript and not all can be solved with typescript.
Although I’m totally in for Typescript, I’m curious about what specific problems you think Typescript can’t solve. It would be nice if you could give some examples.
Isn't this literally just Create-T3-App?
Yes, T3 did a good job to pick up the good stack for the ZenStack world. I actually wrote a post about that:
Why I chose T3 stack as the full-stack to build the react app
JS for ZenStack ・ Dec 6 '22 ・ 6 min read
Instead, ZenStack we are building is a toolkit that is not opinionated toward any stack or framework. The goal is to unleash its full potential for full-stack web app development.