Skip to main content

Using TakeShape with Sapper

· 11 min read
Ashutosh K Singh

In this article, we will discuss how to use TakeShape with Sapper, an application framework powered by Svelte.

In this article, we will discuss how to use TakeShape with Sapper, an application framework powered by Svelte.

If you want to jump right into the code, check out the GitHub Repo here.

And here's a link to the deployed version: https://sapper-takeshape-example.vercel.app/

An animated gif demonstrating the finished website

Prerequisites

  • Knowledge of HTML, CSS, JavaScript
  • Basics of Svelte and GraphQL
  • Node/NPM installed on your local dev machine
  • Any code editor of your choice

What is Svelte?

Svelte is a tool for building fast web applications, similar to JavaScript frameworks like React and Vue, svelte aims to make building slick interactive user interfaces easy. But there's a crucial difference.

What is Sapper?

Sapper is a framework built on top of Svelte and has taken inspiration from Next.js. Sapper helps you create SEO optimized Progressive Web Apps (PWAs) with file system based routing, similar to Next.js.

How to Setup and Install a Sapper project

This tutorial uses sapper-template to quickly set up the initial Sapper project, which is also the preferred way to initialize a Sapper project.

In your project's root directory, run the following commands in the terminal.

npx degit "sveltejs/sapper-template#webpack" sapper-takeshape-example
cd sapper-takeshape-example
npm install
npm run dev

The last command npm run dev will start the development server on port 3000. Head over to http://localhost:3000/.

Here is how your app will look.

How to Generate TakeShape API Keys

To connect to your TakeShape project, you need your API Endpoint and an API key.

If you haven't created a TakeShape project yet, follow this guide to do so.

If you already have a project, your API Endpoint will be in the TakeShape web client. In the home tab, look for the "Useful Snippets" section.

Copy the API Endpoint and save it somewhere secure.

To get an API Key, you'll have to create one. Read our docs on creating an API Key here.

In your project's root directory, run the following command to create a new file named .env to securely store this API key.

touch .env

In your .env file, add the environment variables.

# .env
TAKESHAPE_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
TAKESHAPE_PROJECT="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

To access these environment variables, you need to install the dotenv package, which loads environment variables from the .env file.

Run the following command in the terminal to install the dotenv package in your project.

npm install dotenv

Now you also need to configure Sapper to use these environment variables. Modify your src/server.js file like this.

src/server.js
require("dotenv").config();

import sirv from "sirv";
import polka from "polka";
import compression from "compression";
import * as sapper from "@sapper/server";

const { PORT, NODE_ENV } = process.env;

const dev = NODE_ENV === "development";

polka() // You can also use Express
.use(
compression({ threshold: 0 }),
sirv("static", { dev }),
sapper.middleware()
)
.listen(PORT, (err) => {
if (err) console.log("error", err);
});

In the above code, you have imported the dotenv package at the top of the server.js file.

require("dotenv").config();

Restart your development server by closing it using Ctrl + C and starting it again using npm run dev.

How to Display Posts on the Blog Page

With your development server still running, head over to http://localhost:3000/blog. You will see a page similar to this, which lists all the posts with their links.

These are the sample blog posts that come with the sapper-template and are present in src/routes/blog/_posts.js. You need to update this /blog route to show posts fetched from TakeShape.

Every post in the posts array has a title and a slug, shown on the blog page. You need to create a similar GraphQL query that fetches the title and slug of each post.

On your TakeShape dashboard, navigate to the API tab.

Copy and Paste the following GraphQL query into the API Explorer.

AllPosts
query AllPosts {
allPosts: getPostList {
items {
_id
title
slug
}
}
}

Run this query; you will see an output similar to this.

In Sapper, Page is a Svelte component written in .svelte files. Server routes are modules written in .js files that export functions corresponding to HTTP methods like get, post, etc. Each function receives HTTP request and response objects as arguments, plus a next function.

The index.json.js file under routes/blog directory is a server route, which currently fetches data from the posts array in the _posts.js file. You need to update this server route to fetch posts from TakeShape.

You will need to install the node-fetch package to make the API requests. Run the following command in the terminal to install node-fetch.

npm install node-fetch

Update src/routes/blog/index.json.js file like this and restart your development server.

index.json.js
const fetch = require("node-fetch");

export async function get(req, res) {
const {TAKESHAPE_API_KEY, TAKESHAPE_PROJECT} = process.env;

const data = await fetch(
`https://api.takeshape.io/project/${TAKESHAPE_PROJECT}/graphql`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${TAKESHAPE_API_KEY}`,
},
body: JSON.stringify({
query: `
query AllPosts {
allPosts: getPostList {
items {
_id
title
slug
}
}
}
`,
}),
}
);
const response = await data.json();
const posts = await JSON.stringify(response.data.allPosts.items);

res.writeHead(200, {
"Content-Type": "application/json",
});

res.end(posts)
}

In the above code, you first import the node-fetch package.

const fetch = require("node-fetch");

Then inside the get function, you extract the environment variables from process.env.

const { TAKESHAPE_API_KEY, TAKESHAPE_PROJECT } = process.env;Now, you make the POST request to TakeShape using the fetch method.

Now, you make the POST request to TakeShape using the fetch method.

fetch
const data = await fetch(
`https://api.takeshape.io/project/${TAKESHAPE_PROJECT}/graphql`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${TAKESHAPE_API_KEY}`,
},
body: JSON.stringify({
query: `
query AllPosts {
allPosts: getPostList {
items {
_id
title
slug
}
}
}
`,
}),
}
);

You pass the TakeShape API key under Authorization in headers. The GraphQL query inside the body is the same as discussed above in the API Explorer.

Finally, you return the posts in the response using res.end(posts).

response
const response = await data.json();
const posts = await JSON.stringify(response.data.allPosts.items);

res.writeHead(200, {
"Content-Type": "application/json",
});

res.end(posts);

In Sapper, Page component have a optional preload function that runs before the component is created. As the name suggests this function, preloads the data that the page depends upon.

Preload is the Sapper equivalent to getInitialProps in Next.js or asyncData in Nuxt.js. You can read more about Preloading here.

Open src/routes/blog/index.svelte file in your code editor. Since index.json route is inside blog directory, it can also be referenced as blog.json.

blog.json
<script context="module">
export function preload() {
return this.fetch(`blog.json`)
.then((r) => r.json()).then((posts) => {
return { posts };
});
}
</script>

You fetch data from blog.json route using this.fetch. This method is quite similar to fetch API with some additional features like requesting data based on user's session. You can read more about this.fetch here.

In Svelte, you can iterate over any array or array like value using an #each block as shown here. Here (post._id) is the key that uniquely identifies each post. You can read more about #each block here.

iterating each post
<ul>
{#each posts as post (post._id)}
<li><a rel="prefetch" href="blog/{post.slug}">{post.title}</a></li>
{/each}
</ul>

You don't need to make any other change in index.svelte file except for adding a key in the #each block like shown above.

Navigate to http://localhost:3000/blog in your browser; you will notice that posts have been updated.

You can now delete the _posts.js file in the routes/blog directory.

Since the individual post routes don't exist yet so these links will result in a 404 error, you will create them in the next section.

How to Create Dynamic Routes for Posts

In Sapper, you can create dynamic routes by adding brackets to a page name, ([param]), where the param is the dynamic parameter that is the slug of the article.

You will notice that a file named [slug].svelte already exists in the src/routes/blog directory.

You need to update the server route used in this file so when a user clicks on a post, the data corresponding to that post is fetched and is displayed with the blog/[slug] route.

Update blog/[slug].json.js file like this.

/blog/[slug].json.js
const fetch = require("node-fetch");

export async function get(req, res, next) {
const { slug } = req.params;

const { TAKESHAPE_API_KEY, TAKESHAPE_PROJECT } = process.env;
const data = await fetch(
`https://api.takeshape.io/project/${TAKESHAPE_PROJECT}/graphql`,
{
method: "post",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${TAKESHAPE_API_KEY}`,
},
body: JSON.stringify({
query: `
query PostBySlug($slug: String) {
post: getPostList(where: {slug: {eq: $slug}}) {
items {
_id
title
deck
bodyHtml
}
}
}`,
variables: {
slug: slug,
},
}),
}
);

const response = await data.json();
const post = JSON.stringify(response.data.post.items[0]);

res.writeHead(200, {
"Content-Type": "application/json",
});

res.end(post);
}

The above code is quite similar to the server route code discussed in the last section, with few key differences.

This route fetches individual post data based on the slug provided, which is accessed via req.params.

const { slug } = req.params;

The GraphQL query in the above code fetches post matching the slug using where: { slug: { eq: $slug } }. In the query, bodyHtml corresponds to the HTML body of the post and deck is the short excerpt of the post.

PostBySlug
query PostBySlug($slug: String) {
post: getPostList(where: { slug: { eq: $slug } }) {
items {
_id
title
deck
bodyHtml
}
}
}

The slug is made available to the GraphQL query via variables.

variables
variables: {
slug: slug,
},

Update blog/[slug].svelte file like this.

blog/[slug].svelte
script context="module">
export async function preload({ params }) {
const res = await this.fetch(`blog/${params.slug}.json`);
const data = await res.json();

if (res.status === 200) {
return { post: data };
} else {
this.error(res.status, data.message);
}
}
</script>

<script>
export let post;
</script>

<style>
.content :global(h2) {
font-size: 1.4em;
font-weight: 500;
}

.content :global(pre) {
background-color: #f9f9f9;
box-shadow: inset 1px 1px 5px rgba(0, 0, 0, 0.05);
padding: 0.5em;
border-radius: 2px;
overflow-x: auto;
}

.content :global(pre) :global(code) {
background-color: transparent;
padding: 0;
}

.content :global(ul) {
line-height: 1.5;
}

.content :global(li) {
margin: 0 0 0.5em 0;
}
</style>

<svelte:head>
<title>{post.title}</title>
<meta name="Description" content={post.deck}>
</svelte:head>

<h1>{post.title}</h1>

<div class="content">
{@html post.bodyHtml}
</div>

The preload function takes two arguments, page and session. Here page is an object equivalent to { host, path, params, query } and session is used to pass data such as environment variables from the server.

In the above preload function, you access the page object's params property and pass the slug of the page to the server route.

If you console.log() the page object, you will see all the data available via the page object. Here is how this will look like.

page object
{
host: 'localhost:3000',
path: '/blog/jump-aboard-new-treasure-island-edition',
query: {},
params: { slug: 'jump-aboard-new-treasure-island-edition' }
}

The post is returned based on the status code of the response. this.error is a method in Sapper for handling errors and invalid routes. You can read more about it here.

error handling
if (res.status === 200) {
return { post: data };
} else {
this.error(res.status, data.message);
}

You only need to update post.body to post.bodyHtml in the div with class="content" in [slug].svelte file like.

[slug].svelte
<div class="content">
{@html post.bodyHtml}
</div>

In Svelte, you can render HTML directly into a component using the @html tag like shown above. You can read more about this tag here.

And its done.

Try clicking on any of the posts on /blog route or head over to http://localhost:3000/blog/jump-aboard-new-treasure-island-edition. You will see a page similar to this.

You can view the finished website here and the code for the project here.

Conclusion

In this article, you learned how to use TakeShape with Sapper, an application framework powered by Svelte. We saw how simple it is to integrate TakeShape with Sapper.

We also discussed how to use API Explorer in TakeShape and how to use the preload function in Sapper.

With just a few simple modifications and updates in your Sapper project, you can easily achieve a perfect Lighthouse score. Amazing, right?

Here are some additional resources that can be helpful.

Happy coding!