Good bye API layer. Uniting frontend and backend

Published 5/30/2020

In my first job, besides web development, there was also this application written in PowerBuilder. A rather old restrictive language to create data-driven cruddy Windows applications...

One thing that stood out for me, however, was the ease in which to access the database. On the event listener of a button, you can simply access or write to the database (or directly call a dedicated service that would do so).

Here is an example of their demos to connect to the database on a button clicked event:

powerbuilder code

yikes... But its ease of use is incredible!

Now let's look at the web. Backend and frontend are separate pieces. It doesn't matter how you look at it. If you have server driven applications, or an SPA that accesses your API, there is a clear separation.

You can't possibly have something like a database query running directly in the event listener of a button click. Well... What if I told you, it is both possible and secure to do so.

// FRONTEND
// resources/js/main.js

import { getUser } from '@/app/Actions/users.js'

getUser(1).then(user => {
  document.getElementById('app').innerHTML = JSON.stringify(user)
})
// BACKEND
// app/Actions/users.js

import User from '@/app/Models/User'

exports.getUser = async (id) => {
  return User.findOrFail(id)
}

Check out my e-book!

Learn to simplify day-to-day code and the balance between over- and under-engineering.

So a script on the frontend simply imports a function from the backend and calls it to get the user.

Not mindblowing? Okay, how about this?

// FRONTEND
// resources/js/main.js

import { getUser } from '@/app/Actions/users.php'

getUser(1).then(user => {
  document.getElementById('app').innerHTML = JSON.stringify(user)
})

In case you missed it, pay close attention to this line:

import { getUser } from '@/app/Actions/users.php'

Let's zoom in some more: '@/app/Actions/users.php'. And some more .php.

Yes, the approach is not limited to Node.js but can work with possibly any backend language.

So what's going on? Obviously we got rid of the API layer, but how?

Well, honestly, we haven't, we just swept it under the carpet. That means when you call getUser it will still perform an ajax request to the server. There will still be an api route on the backend. But all of that boilerplate and dealing with HTTP is poof gone.

No more fetch requests to the backend, no more setting up API routes, no need for controllers. That means if you want to find out what the ajax request is doing, you no longer have to track down the routes file, go to the controller which again just goes to some service file. Just (ctrl/cmd) + click on the function. It's seamless.

How it works

It's surprisingly simple. A roughly 10 line webpack loader (could be rollup, TS, etc.) on the frontend that intercepts module resolution for files from the backend. Instead of importing the backend code, it will import a function that performs an HTTP request for you pointing to the correct route. At the same time, the backend will automatically create the JSON API for all files inside the "actions" folder. So you can still use the JSON API are you in need of a mobile app for example.

Check it out here if you want to give it a try: https://github.com/MZanggl/byebye-api-prototype.

This is obviously still a prototype. But you might have seen something similar already in frameworks like Blitzjs.


I also prepared an example with adonis.js on the backend and vue.js on the frontend to give a more real world example that also covers some more use cases like auth: https://github.com/MZanggl/adonis-vue-without-api.

This example does things a little differently. The backend generates the routes into a file that the frontend then makes use of for its fetch requests. This way, you don't need to write a language parser for each backend language and there is a single source of truth.