The code for this project is available in the StepZen-dev examples repo on GitHub

Frameworks and libraries such as React, Vue, and Svelte can all connect to GraphQL APIs to fetch the data needed to power web applications. Over the last few years there has been an influx of "metaframeworks" that provide a larger feature set such as static generation, server-side rendering, and serverless function support. Prominent examples of these metaframeworks include Next.js, Remix, RedwoodJS, and Nuxt.js.

SvelteKit is a new Svelte metaframework for building web applications with filesystem-based routing. It is inspired by Next.js and includes similar features such as server-side rendering and automatic generation of API endpoints. It is considered "serverless first" because it is designed to run on serverless infrastructure such as AWS Lambda and Cloudflare Workers.

SvelteKit's capabilities are well suited for connecting to a GraphQL endpoint built and running on StepZen because StepZen runs your queries on the server, not the client. This ensures that your API keys are protected.

This example uses the DEV API as a headless CMS to build a personal blog site. With StepZen's @rest directive, DEV's REST API is turned into a GraphQL API. This allows the developer to query across multiple endpoints at once and removes the need to do any transformation of the resulting query response through JavaScript code.

Setup Your Project

To generate a new boilerplate SvelteKit application, use npm init svelte@next and give your project a name.

npm init svelte@next stepzen-sveltekit-blog

Select Skeleton project

✔ Which Svelte app template? › Skeleton project

You are then asked a series of questions to configure your application. Answer based on your own preferences and use case.

For the sake of simplicity in this example, I answered no for TypeScript, ESLint, and Prettier and don't include any additional CSS libraries.

✔ Use TypeScript? … No / Yes
✔ Add ESLint for code linting? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

Create Your GraphQL Schema Files

Navigate into your project directory and create a directory for your StepZen files.

cd stepzen-sveltekit-blog
mkdir -p stepzen/schema

Create an index.graphql file - it allows us to easily connect all of our schemas. Also create a schema file each for our user type (user.graphql) and articles type (articles.graphql).

touch stepzen/index.graphql \
  stepzen/schema/user.graphql \
  stepzen/schema/articles.graphql

For more information about how to setup your StepZen project, visit the StepZen documentation.

Configure StepZen

Create a configuration file called stepzen.config.json. This specifies the name of our GraphQL endpoint and the location of StepZen files within the SvelteKit project. (See CLI Configuration for more information.)

echo '{"endpoint": "api/stepzen-sveltekit-blog", "root": "stepzen"}' > stepzen.config.json

Create a file called config.yaml for holding your DEV API keys. Our config.yaml file contains API keys to authenticate with the third-party APIs used by our project. (See Manage Configuration and Keys for more information.)

echo 'configurationset:' > stepzen/config.yaml

You can find your API keys by visiting dev.to/settings/account and scrolling to DEV Community API Keys. Include the following code in config.yaml.

configurationset:
  - configuration:
      name: devto_config
      devto_api_key: YOUR_KEY_HERE

Finally, make sure to add config.yaml to your .gitignore file:

.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
config.yaml

StepZen Schema

The index.graphql file in your StepZen project is a manifest of all the individual schema (.graphql) files we use in the project. In this example, there is a user.graphql file and an articles.graphql file contained in a schema directory, so index.graphql looks like this:

# stepzen/index.graphql

schema
  @sdl(
    files: [
      "schema/user.graphql"
      "schema/articles.graphql"
    ]
  ) {
  query: Query
}

Create User Type

The DevToUser type includes information about authors on the DEV platform. This information is going to be displayed on the home page of our blog. You can use our JSON2SDL tool to automatically generate GraphQL types.

# stepzen/schema/user.graphql

type DevToUser {
  github_username: String
  id: Int
  joined_at: String
  location: String
  name: String
  profile_image: String
  summary: String
  twitter_username: String
  type_of: String
  username: String
  website_url: String
}

The getUser query returns the DevToUser type and takes the user's name as an argument.

# stepzen/schema/user.graphql

type Query {
  getUser(
    id: String!, url: String
  ): DevToUser
    @rest(endpoint: "https://dev.to/api/users/$id")
}

Deploy Your GraphQL Endpoint on StepZen

Let's test our GraphQL endpoint is deployed and running on StepZen before we start building out our frontend. 1. Make sure you have the StepZen CLI installed 2. Deploy your endpoint with the stepzen start command.

stepzen start

This deploys your endpoint and starts a GraphiQL editor on localhost:5001. In GraphiQL, test your endpoint with your own username in place of ajcwebdev.

query GET_AJCWEBDEV {
  getUser(id: "by_username", url: "ajcwebdev") {
    github_username
    id
    joined_at
    location
    name
    profile_image
    summary
    twitter_username
    type_of
    username
    website_url
  }
}

Building the SvelteKit Frontend

Before we can run our SvelteKit project, we need to install our dependencies.

npm i
npm run dev

npm run dev starts your development server. Open localhost:3000 to see the project.

01-hello-world-localhost-3000

Now that our GraphQL API is deployed and our Svelte application is running, let's query our endpoint and display the data on the home page.

Pages

Pages are Svelte components written in .svelte files. The filename determines the route. For example, src/routes/index.svelte is the root of your site.

A .svelte file contains three parts:

  • <script> for JavaScript
  • <style> for CSS
  • Any markup you want to include with HTML.
<!-- src/routes/index.svelte -->

<script></script>

<section></section>

<style></style>

By default, when a user first visits the application, they are served a server-rendered version of the page in question. JavaScript is served to 'hydrate' the page and initialize a client-side router.

Navigating to other pages is handled on the client and common portions in the layout do not need to be rerendered. Pages typically generate HTML to display to the user (as well as any CSS and JavaScript needed for the page).

Endpoints

Endpoints are modules written in .js (or .ts) files that export functions corresponding to HTTP methods. Create an endpoint file called user.json.js for the user query.

touch src/routes/user.json.js

Endpoints run only on the server (or when you build your site, if pre-rendering). Pages can request data from endpoints. Endpoints return JSON by default, though may also return data in other formats.

// src/routes/user.json.js

export async function post() {
  const response = await fetch()
  const data = await response.json()
  
  if (data) {
    return {
      body: data
    }
  }
}

A component that defines a page or a layout can export a load function that runs before the component is created and receive an implementation of fetch. This endpoint and corresponding function can be used to:

  • Access cookies on the server
  • Make requests against the app's own endpoints without issuing HTTP calls
  • Make a copy of the response and then send it embedded in the initial page load for hydration

Since endpoints only run on the server, they can be used for requests with private API keys that can't be exposed on the client. This also means you'll want to set those API keys to environment variables.

npm i -D dotenv
echo 'STEPZEN_ENDPOINT=\nSTEPZEN_API_KEY=\nDEV_TO_USERNAME=' > .env

Include your StepZen endpoint, StepZen API key, and DEV username in .env.

STEPZEN_ENDPOINT=
STEPZEN_API_KEY=
DEV_TO_USERNAME=

Fill in the rest of the POST request with our GraphQL getUser query and environment variables.

// src/routes/user.json.js

import 'dotenv/config'

const { STEPZEN_ENDPOINT, STEPZEN_API_KEY, DEV_TO_USERNAME } = process.env

export async function post() {
  const response = await fetch(STEPZEN_ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `apikey ${STEPZEN_API_KEY}`
    },
    body: JSON.stringify({
      query: `{
        getUser(id: "by_username", url: "${DEV_TO_USERNAME}") {
          name
          summary
          github_username
          location
          profile_image
        }
      }`
    })
  })

  const data = await response.json()
  
  if (data) {
    return {
      body: data
    }
  }
}

Load Function

load is similar to getStaticProps or getServerSideProps in Next.js, except that it runs on both the server and the client. <script context="module"> is necessary because load runs before the component is rendered. Code that is per-component instance should go into a second <script> tag.

<!-- src/routes/index.svelte -->

<script context="module">
  export const load = async ({ fetch }) => {
    try {
      const response = await fetch('/user.json', {
        method: 'POST',
        credentials: 'same-origin',
        headers: {
          'Content-Type': 'application/json'
        }
      })
      return {
        props: { ...(await response.json()) }
      }
    } catch (error) {
      console.error(`Error in load function for /: ${error}`)
    }
  }
</script>

<script>
  export let data
</script>

Underneath the script tags, create a user component. The necessary pieces of user data can be accessed in the data.getUser object.

<!-- src/routes/index.svelte -->

<main>
  <h2>{data.getUser.name}</h2>
  <h3>{data.getUser.github_username} - {data.getUser.location}</h3>
  <p>{data.getUser.summary}</p>
  <img src="{data.getUser.profile_image}" alt="profile pic">
</main>

Return to localhost:3000 to see the component.

02-home-page-with-user-component

Articles Schema

Now we want to be able to query for our blog posts. Include the following code in articles.graphql.

# stepzen/schema/articles.graphql

type Article {
  body_html: String
  body_markdown: String
  canonical_url: String
  comments_count: Int
  cover_image: String
  description: String
  path: String
  public_reactions_count: Int
  published_at: String
  readable_publish_date: String
  slug: String
  tags: String
  tag_list: JSON
  title: String
  url: String
  user: DevToUser
}

type Query {
  getArticleByPath(
    slug: String!, username: String!
  ): Article
    @rest(endpoint: "https://dev.to/api/articles/$username/$slug")

  getArticles(
    collection_id: Int
    page: Int
    per_page: Int
    state: String
    tag: String
    tags: String
    tags_exclude: String
    top: Int
    username: String
  ): [Article]
    @rest(endpoint: "https://dev.to/api/articles")
}

This defines the types for our blog articles and queries for getting individual articles or multiple articles.

Articles Query

If you want to test your articles query against the GraphQL endpoint you deployed to StepZen, use the following query with your username included instead of ajcwebdev:

query GET_AJCWEBDEV_ARTICLES {
  getArticles(username: "ajcwebdev", per_page: 100) {
    title
    description
    readable_publish_date
    cover_image
    tags
    public_reactions_count
    slug    
    url
  }
}

This returns the user's first hundred blog posts.

Articles Endpoint

To query for our articles, we'll create an articles.json.js file.

touch src/routes/articles.json.js

The articles endpoint in articles.json.js is very similar to the user endpoint. The only difference is the GraphQL query inside the body.

// src/routes/articles.json.js 

import 'dotenv/config'

const { STEPZEN_ENDPOINT, STEPZEN_API_KEY, DEV_TO_USERNAME } = process.env

export async function post() {
  const response = await fetch(STEPZEN_ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `apikey ${STEPZEN_API_KEY}`
    },
    body: JSON.stringify({
      query: `{
        getArticles(username: "${DEV_TO_USERNAME}", per_page: 100) {
          title
          description
          readable_publish_date
          cover_image
          tags
          public_reactions_count
          slug
          url
        }
      }`
    })
  })

  const data = await response.json()
  
  if (data) {
    return {
      body: data
    }
  }
}

Articles Svelte Component

With our endpoint created, we need a Svelte component for our page. Create an articles.svelte file.

touch src/routes/articles.svelte

The articles.svelte component is very similar to the component in index.svelte. It has a load function that is fetching from articles.json instead of user.json.

<!-- src/routes/articles.svelte -->

<script context="module">
  export const load = async ({ fetch }) => {
    try {
      const response = await fetch('/articles.json', {
        method: 'POST',
        credentials: 'same-origin',
        headers: {
          'Content-Type': 'application/json'
        }
      })
      return {
        props: { ...(await response.json()) }
      }
    } catch (error) {
      console.error(`Error in load function for /: ${error}`)
    }
  }
</script>

<script>
  export let data
</script>

Since we have multiple blog posts, we need to loop over an array of articles returned from the GraphQL query. Svelte includes built-in syntax for looping over an array with an each block.

<!-- src/routes/articles.svelte -->

<main>
  {#each data.getArticles as {
    title, description, readable_publish_date, cover_image, tags, slug
  }}
    <div>
      <a target="_blank" href="https://dev.to/ajcwebdev/{slug}">
        <h2>{title}</h2>
      </a>
      <h3>{readable_publish_date} - {tags}</h3>
      <img src={cover_image} width="500" style="display: block; margin: auto;">
      <p>{description}</p>
    </div>
  {/each}
</main>

This loops over the response from getArticles and creates a list of blog posts that includes the title, date published, tags, and description. Each title also includes a link to the blog post. Open localhost:3000/articles to see a list of your articles.

Final User Endpoint

It would be nice to also be able to display recent blog posts on the home page. To do so, modify the main user component to include the getArticles query and specify 5 blog posts per page.

// src/routes/user.json.js

import 'dotenv/config'

const { STEPZEN_ENDPOINT, STEPZEN_API_KEY, DEV_TO_USERNAME } = process.env

export async function post() {
  const response = await fetch(STEPZEN_ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `apikey ${STEPZEN_API_KEY}`
    },
    body: JSON.stringify({
      query: `{
        getUser(id: "by_username", url: "${DEV_TO_USERNAME}") {
          name
          summary
          github_username
          location
          profile_image
        }
        getArticles(username: "${DEV_TO_USERNAME}", per_page: 5) {
          title
          description
          readable_publish_date
          slug
        }
      }`
    })
  })

  const data = await response.json()
  
  if (data) {
    return {
      body: data
    }
  }
}

Add an each loop on the home page and create a list of article titles. Each article title has a link to the blog post.

<!-- src/routes/index.svelte -->

<main>
  <h2>{data.getUser.name}</h2>
  <h3>{data.getUser.github_username} - {data.getUser.location}</h3>
  <p>{data.getUser.summary}</p>
  <img src="{data.getUser.profile_image}" alt="profile pic">

  <h3>Most Recent Articles</h3>
  {#each data.getArticles as { title, slug }}
    <ul>
      <a target="_blank" href="https://dev.to/ajcwebdev/{slug}">
        <li>{title}</li>
      </a>
    </ul>
  {/each}
</main>

Svelte Config and Deployment Adapters

Svelte apps are built with adapters, which help optimize your project to deploy with different environments. This project uses the adapter-netlify for Netlify.

// svelte.config.js

import adapter from '@sveltejs/adapter-netlify';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter({
      split: false
    }),
    target: '#svelte'
  }
};

export default config;

target hydrates the <div id="svelte"> element in src/app.html.

Deploy to Netlify

Create a netlify.toml file for our build instructions.

touch netlify.toml

Set the build command to npm run build and the publish directory to build.

[build]
  command = "npm run build"
  publish = "build"

To deploy the site, push the project code to a GitHub repository and link that repository to your Netlify account. The build instructions are imported from the netlify.toml file.

You can view a deployed example of this project at ajcwebdev-stepzen-sveltekit.netlify.app.

Conclusion

In this article, we've created a full SvelteKit project from scratch that queries a GraphQL endpoint whose backend is the DEV REST interface (deployed on StepZen). The GraphQL endpoint provides our web application with data about a user and their DEV blog posts. The project is configured for automatic deployment to Netlify and can be modified for the user's own needs.

The DEV API also provides information such as the user's organization, blog comments, and podcasts. If you want to build out this example further, grab the DEV schema in the StepZen GraphQL Studio. For more examples like this, see the StepZen-dev examples repo on GitHub.