The “It’s Complicated” Relationship Between Client & Server

Of all the relationships that exist within the tech universe, perhaps none is quite as “complicated” yet synchronous as that between a client and its server. As someone who learned computer science mostly coding on the front-end (is it acceptable to call CSS my favorite language?), working with servers was unexplored territory for me when I started my Google internship just one month ago. Today, however, the client/server relationship is one I have explored thoroughly and, while certainly complicated, one which is essential to understand. Below, I’ve outlined the key characteristics of this technological romance.

The client is needy.

The most fundamental part of the client/server relationship is that the client needs its server. A new web app I am currently developing displays an HTML page (with JavaScript) for a user to login to their Google account, then uses its server (a Google Cloud Function) to make a call to the Google Sheets API. GCF acts as a wonderful option here due to its being a lightweight & server-less back-end.

This back-end action — of making a call to the Sheets API — is done server-side. Why? Because when you’re collecting data, the server adds security the client does not offer. In my applet, using a server protects my API keys. So, while the client is great for displaying text, buttons, and prompts to the user, it is not ideal for every task.

After a user has logged into her Google account, this authorization needs to be stored in order to be able to create a new Sheet in her account. The client can take complete control of this authorization flow, using the Google sign-in JavaScript client to obtain & store necessary credentials.

function setAuthToken() {
var googleAuth = gapi.auth2.getAuthInstance();
var googleUser = googleAuth.currentUser.get();
var authResponse = googleUser.getAuthResponse(true);
AUTH_PARAM = authResponse.access_token;
console.log("Auth param is: " + AUTH_PARAM);
        createSheet(AUTH_PARAM);
}

Here, ‘AUTH_PARAM’ is my strange little nickname for ‘authorization parameter’.

However, this is where the client gets stuck. Even though it has every level of authorization necessary, the client cannot securely make a call to the Sheets API without using its server. Inside createSheet(), the client now sends a (fetch!) request to the server in order to complete this task…how needy.

The server can be controlling.

Instead of letting the client continue and make calls to APIs, the server likes to control these back-end executions. In my previous blog post I discuss the use of fetch requests to call Google Cloud Functions, and that is exactly what I am using in this web app to access the Google Sheets API.

Here’s how the baton gets passed over to the server:

function createSheet(AUTH_PARAM) {
var data = {'auth': AUTH_PARAM};
    fetch(functionEndpoint, {
method: 'POST',
body: JSON.stringify(data),
headers:{
'Content-Type': 'application/json'
}
}).then(res => {
// handle response!
}

They have communication issues.

Now that the client has sent a message (or request) over to the server, the server needs to properly grasp & understand this request in order to perform it correctly. In the above code, the client has sent a ‘POST’ request, meaning that the parameter is sent securely, as opposed to ‘GET’ requests, where a parameter is sent as a part of the function endpoint URL. Trust issues? Maybe.

The takeaway here is that a client and server can’t communicate freely — the client needs to specify whether her messages should be kept confidential.

After receiving a ‘POST’ request, the server needs to obtain necessary parameters for function execution.

const authToken = req.body.auth;

Alternatively, in a ‘GET’ request, the server would instead access parameters through the query:

const authToken = req.query.auth;

Its a small difference, but mixing up body versus query here could break your code. See — communication really is key. Especially when your client speaks JavaScript and your server speaks Node.js.

The server can get bossy.

Once necessary information is sent from client to server, it is now the latter’s turn to complete its very important back-end tasks. However, the server needs to do some additional set-up and “bossing around” in order to execute what it’s meant to do.

async function createSheet(authToken) {
const oauth2Client = new google.auth.OAuth2();
oauth2Client.setCredentials({
access_token: authToken
});

You can view the full code in my GitHub.

Above, we first set the client’s authorization to a new OAuth2 instance, which is then sent in as a part of the Sheets API request. This effectively creates a new Sheet in the Google account of the end-user, who previously logged in through the client.

“It’s Complicated” but it works.

As complex as the relationship may seem, it soon becomes clear that the client and server work synchronously well together to execute tasks one alone cannot perform. A server is more powerful regarding task execution, while a client has complete control of how an end-user interacts on the surface. It’s a Romeo and Juliet kind of romance, where neither can exist without the other.

If you’re interested in playing around more with clients and servers, consider using Google Cloud Functions to handle your back-end. Not only does GCF have a generous free tier, it also supports languages like JavaScript, Python, Go, and their native libraries. So, don’t worry if Node.js isn’t really your type — there are plenty of other fish in the GCF sea.

Let me know how you’re using client/server relationships to upgrade the functionalities of your web applications. I’d love to hear from you!