tutorial // Feb 04, 2022
How to Fetch and Render Data in Joystick Components
Adding the data option to Joystick components to fetch data on the server and render it in components on the server and the client.

Getting Started
For this tutorial, we're going to use CheatCode's full-stack JavaScript framework, Joystick. Joystick brings together a front-end UI framework with a Node.js back-end for building apps.
To begin, we'll want to install Joystick via NPM. Make sure you're using Node.js 16+ before installing to ensure compatibility (give this tutorial a read first if you need to learn how to install Node.js or run multiple versions on your computer):
Terminal
npm i -g @joystick.js/cli
This will install Joystick globally on your computer. Once installed, next, let's create a fresh project:
Terminal
joystick create app
After a few seconds, you will see a message logged out to cd
into your new project and run joystick start
. Before you run this, we need to install one additional dependency, node-fetch
:
Terminal
cd app && npm i node-fetch
Once this is installed, from the same app
directory you just cd
'd into, you can start up the app:
Terminal
joystick start
After this, your app should be running and we're ready to get started.
Wiring up an API endpoint using getters
The first thing we need to do is get access to some data that we'll render in our component. While we could just render some static (or hard-coded) data, it'd be better to pull some data from a third-party API so we can see the power and flexibility of this technique.
/api/index.js
import fetch from 'node-fetch';
import { URL, URLSearchParams } from 'url';
export default {
getters: {
posts: {
get: (input = {}) => {
const url = new URL('https://jsonplaceholder.typicode.com/posts');
if (input?.id) {
const searchParams = new URLSearchParams(input);
url.search = searchParams.toString();
}
return fetch(url).then((response) => response.json());
},
},
},
setters: {},
};
In a Joystick application, "getters" allow us to define API endpoints for "getting" data. Behind the scenes, getters are turned into plain HTTP REST API endpoints in your app (e.g., http://localhost:2600/api/_getters/posts
).
Above, we're defining a new getter called posts
which will get a list of posts from the JSON Placeholder API—a free REST API that provides test data for testing and prototyping.
Getters are one of two types of API endpoints in a Joystick app with the other being setters (these "set" data in our application—the "create, update, and delete" part of CRUD). In a Joystick app, getters and setters are defined together on a single object exported from the /api/index.js
file we see above (referred to as your API's "schema" in Joystick).
This object is then imported into /index.server.js
and passed as part of the options to the node.app()
function—as api
—from the @joystick.js/node
package. This tells Joystick to automatically load all of the getters and setters defined in the file we see above when it starts up the server side of our app.
For this tutorial, we're defining a single getter posts
which returns data from the JSON Placeholder API. To make it work, we add a new property posts
to the object assigned to getters
which itself is assigned an object.
That object contains a property get
which is assigned to a function that's responsible for "getting" our data and returning it to the HTTP request that called the getter. Inside of that function, we begin by create an instance of a URL object via the new URL()
constructor (notice we've imported this up top from the url
package—this is built-in to Node.js and we do not need to install it separately).
To that constructor, we pass the URL that we want to create the object for. In this case, we want to use the /posts
endpoint from the JSON Placeholder API located at https://jsonplaceholder.typicode.com/posts
.
Next, we make a check to see if our getter was passed any input
variables when it was called (how this works will make more sense later, but think of this like being passed as a POST
body to an HTTP request). If we have an id
value defined on our input (the ID of a post on the JSON Placeholder API like 1
or 5
), we want to create a new instance of the URLSearchParams
class, passing in our input object. Here, each property on the object will be turned into a query parameter. For example, an input
value of...
{ id: 5 }
will be turned into...
?id=5
To make that value useful, we set the .search
property of the url
object we created above to the value of searchParams
cast as a string value (using the .toString()
function).
Finally, with our complete url
object, we call to the fetch()
function we imported from the node-fetch
package up top, passing the url
object (fetch
understands how to interpret this object). Because we expect fetch()
to return us a JavaScript Promise, on the end, we call to .then()
to say "after we get a response then do this."
The "this" that we're doing is taking the response
object and converting it into a JSON format with the .json()
method. What we expect to return from this chain of methods is an array of objects representing posts from the JSON Placeholder API.
With this in place, now we're ready to get our data wired up. To do that, we're going to need a route where we can render the component that we're going to create. Real quick, let's jump over to the /index.server.js
file and set up that route.
Wiring up a route for our component
If we open up the /index.server.js
file at the root of our app, we'll see that the joystick create app
function we called earlier created a file that automatically imports and runs node.app()
for us along with some example routes.
/index.server.js
import node from "@joystick.js/node";
import api from "./api";
node.app({
api,
routes: {
"/": (req, res) => {
res.render("ui/pages/index/index.js", {
layout: "ui/layouts/app/index.js",
});
},
"/posts": (req, res) => {
res.render("ui/pages/posts/index.js");
},
"*": (req, res) => {
res.render("ui/pages/error/index.js", {
layout: "ui/layouts/app/index.js",
props: {
statusCode: 404,
},
});
},
},
});
By default, a root route at /
and a catch-all or 404
route at *
(meaning, everything that doesn't match a route above this one) are pre-defined for us. Here, we've added an additional route /posts
. To that route, we've assigned a function to handle the inbound request taking in the req
and res
objects. Though it may not look like it, behind the scenes, Joystick turns this into a plain Express.js route, similar to us writing app.get('/posts', (req, res) => { ... })
.
Inside of that function, we make a call to a special function added by Joystick to the res
object called .render()
. This function, like the name implies, is designed to render a Joystick component in response to a request. To it, we pass the path to a component in our app that we want it to render, along with an object of options (if necessary, which it isn't here so we've omitted it).
When this route is matched in a browser, Joystick will go and get this component and server-side render it into HTML for us and send that HTML back to the browser. Internally, res.render()
is aware of the data
option on Joystick components. If it sees this on a component, it "scoops up" the call and fetches the data as part of the server-side rendering process.
This is how we're going to call to the posts
getter we defined above. Our goal being to make it so that when our page loads, we get back server-side rendered HTML with out data already loaded in it.
Next, we need to actually create the component at the path we're passing to res.render()
above.
Wiring up a Joystick component with data from the API
To start, first, we need to add the file we assumed will exist at /ui/pages/posts/index.js
:
/ui/pages/posts/index.js
import ui from '@joystick.js/ui';
const Posts = ui.component({
render: () => {
return `
<div>
</div>
`;
},
});
export default Posts;
Here, we're just adding a skeleton component using the ui.component()
function imported from the @joystick.js/ui
package (automatically installed for us by joystick create
).
In the HTML string we return from our render
function, for now we're just rendering an empty <div></div>
. If we visit the route we added on the server in our browser at http://localhost:2600/posts
, we should see a blank white page.
Now we're ready to wire up our data. Let's add everything we need and walk through it (we don't need much code):
/ui/pages/posts/index.js
import ui from '@joystick.js/ui';
const Posts = ui.component({
data: async (api = {}, req = {}, input = {}) => {
return {
posts: await api.get('posts', {
input,
}),
};
},
render: ({ data, each }) => {
return `
<div>
<ul>
${each(data?.posts, (post) => {
return `
<li>
<h4>${post.title}</h4>
<p>${post?.body?.slice(0, 80)}...</p>
</li>
`;
})}
</ul>
</div>
`;
},
});
export default Posts;
Believe it or not, this is all we need to get our data fetched and server-side rendered in our app and rendered in the browser.
At the top of our component definition, we've added a new option data
assigned to a function. This function receives three arguments:
api
which is an object containing an isomorphic (meaning it works in the browser and on the server) version of theget()
andset()
functions built-in to both@joystick.js/ui
and@joystick.js/node
for calling to our getters and setters.req
which is a browser-safe version of the inbound HTTP request (this gives us access toreq.params
andreq.context.user
so we can reference them when fetching data).input
any input data passed when refetching data via thedata.refetch()
method (we'll cover this in a bit).
Inside of that function, we return an object that we want to assign as the value of data
on our component instance. Here, because we want to get back a list of posts, we define a property posts
and set it equal to a call to api.get('posts')
where the 'posts'
part is the name of the getter we defined earlier in the tutorial.
Because we expect an array of objects representing our posts to be returned from that getter, we assign our call directly to that function, prefixing the await
keyword (and adding async
to the function we pass to data
) to tell JavaScript to wait until this call responds before continuing to interpret the code.
The end result here is that on the server, our data is fetched automatically and set to the data
property on our component instance. Down in the render
function, we can see that we've added a call to destructure or "pluck off" a data
and each
property from the argument passed to the render function (this is an object representing the component instance).
Instead of doing this, we could pass a single argument
component
to the render function and then write something likecomponent.data
andcomponent.each
to reference the exact same values as we are here.
Down in our HTML, we've added a <ul></ul>
unordered list tag, and inside of it, we're using the JavaScript interpolation ${}
syntax to say "in these brackets, call the each()
function passing the value of data.posts
."
That function, each()
will loop over the array of posts we're passing it and for each one, return a string of HTML from the function we pass as the second argument to it. That function takes in the current item or, in this case, post
being looped over for use in the HTML being returned.
Here, we output the title
of each post and a truncated version of the body
for each post in the array.
If we load up our browser now, we should see some posts rendering in the browser.
While we're technically done, before we wrap up, let's quickly learn how to refetch data after the initial page load.
/ui/pages/posts/index.js
import ui from '@joystick.js/ui';
const Posts = ui.component({
data: async (api = {}, req = {}, input = {}) => {
return {
posts: await api.get('posts', {
input,
}),
};
},
events: {
'submit form': (event, component) => {
event.preventDefault();
const input = component.DOMNode.querySelector('input');
if (input.value) {
component.data.refetch({ id: input.value });
} else {
component.data.refetch();
}
},
},
render: ({ data, each }) => {
return `
<div>
<form>
<input type="text" placeholder="Type a post ID here..." />
<button type="submit">Get Post</button>
</form>
<ul>
${each(data?.posts, (post) => {
return `
<li>
<h4>${post.title}</h4>
<p>${post?.body?.slice(0, 80)}...</p>
</li>
`;
})}
</ul>
</div>
`;
},
});
export default Posts;
If we're building a non-trivial UI, it's likely that at some point we'll want to refetch data based on some sort of user interaction, or, on some interval (e.g., polling for new data every 5 seconds).
On the data
property assigned to our component instance, Joystick gives us a .refetch()
method that we can call to perform a refetch on-demand. If we look at the HTML returned from our render()
function, we can see that we've added a few more lines, adding in a simple <form></form>
with an input and a button.
Recall that earlier on the server when we defined our getter, we added the potential for an id
to be passed so we could fetch a specific post. By default, we're not passing anything, but to demonstrate our use of data.refetch()
(and the ability to pass input values to it), here, we're adding an event listener for our form's submit
event to do exactly that.
Looking at the events
property we've added to our component definition, when our form is submitted, first, we want to make sure we call to the event.preventDefault()
function on the event
argument we're passed (this is the browser DOM event as it's happening) to prevent the standard or built-in form submission handler from being called in the browser (this triggers a page refresh that we want to skip).
Beneath this, we take the component
instance that's automatically passed as the second property to our event handlers in Joystick. On that object, a DOMNode
property is added which gives us access to the current component as it's rendered in the browser (the code we write here—our Joystick component—is just an abstraction for generating these DOM nodes dynamically).
On that component.DOMNode
value we call the querySelector
method, passing in the selector of an element we want to access. Here, we want to get the <input />
tag that's rendered in our component. In return, we expect to get back the DOM node for that input element (why we're storing it in a variable called input
).
Beneath this, we conditionally call to component.data.refetch()
based on whether or not our input
has a value. If it does, we want to pass that value as the id
property on our input object. Here, the object we pass to component.data.refetch()
is automatically assigned to the input
value we pass to the server when we call api.get('posts')
up in our data
function.
If input.value
is empty, we want to skip passing any input.
The end result of this is that if we do pass a value (the ID of a post, e.g., 1
or 5
), we'll pass that to our getter and expect to get back a single post from the JSON Placeholder API. If we do not pass a value, we'll expect the default response of our full list of posts.
Back in the browser, if we load this up and type a number into the input and hit "Get Post," we should see our list automatically reduced down to that one post. If we remove the number and hit "Get Posts" again, we should see the full list restored.
Wrapping up
In this tutorial, we learned how to wire up an API endpoint using the getters feature in Joystick that we call from a component using the Joystick data
property to automatically fetch and server-side render our HTML with the data inside. We also learned how to render a component via a route using the res.render()
method in Joystick and how to refetch data inside of a component in response to a user's behavior.