Create a blog with Svelte and DEV.to API
Tags: svelte, javascript, tutorial, blog
Published: 2019-11-24 21:50:37

---
title: Create a blog with Svelte and DEV.to API
published: true
description: How to create a blog using dev.to API and svelte
tags: svelte, javascript, tutorial, blog
series: svelte
---


We are going to create a Blog using dev.to API for retrieving our posts and finally deploy it with Now.

Demo: https://jpblanco.dev
Source: https://github.com/JPBlancoDB/svelte-blog

### First step: Scaffolding our app. 

Let's scaffold our new project with [Sapper](https://sapper.svelte.dev/) by cloning their repo as their official guide dictates:

```bash
npx degit "sveltejs/sapper-template#rollup" my-app
# or: npx degit "sveltejs/sapper-template#webpack" my-app
cd my-app
npm install
npm run dev
```

In this case, I've chosen webpack but it is really up to you. This is not going to affect the rest of the post neither I'm going to explain differences between bundlers.

Then, it would be:

```bash
npx degit "sveltejs/sapper-template#webpack" svelte-blog
cd svelte-blog
npm install
npm run dev
```

If we go to http://localhost:3000 we should see that everything is working as expected (and also Borat :joy:).

Lastly, let's remove all the files we are not going to use (at least, it is not within this article).

```bash
rm -rf cypress cypress.json src/components/Nav.svelte src/routes/blog/ src/routes/about.svelte 
```

### Second step: Fix the base template

As we've removed `src/components/Nav.svelte`, we should fix the layout by removing the dependency in our `_layout.svelte` file.

```html
<style>
  main {
    position: relative;
    max-width: 56em;
    background-color: white;
    padding: 2em;
    margin: 0 auto;
    box-sizing: border-box;
  }
</style>

<main>
  <slot />
</main>

```  

And we could also add a global title for our blog, this could be achieved by adding `svelte:head` tag to our component:

```html
<svelte:head>
  <title>Juan Pablo Blanco - Blog</title>
</svelte:head>
```

> The <svelte:head> element allows you to insert elements inside the `<head>` of your document.

You could read more about `svelte:head` in the official doc: [Svelte Head](https://svelte.dev/tutorial/svelte-head)


So the end result would be:

```html
<style>
  main {
    position: relative;
    max-width: 56em;
    background-color: white;
    padding: 2em;
    margin: 0 auto;
    box-sizing: border-box;
  }
</style>

<svelte:head>
  <title>Juan Pablo Blanco - Blog</title>
</svelte:head>

<main>
  <slot />
</main>

```

### Third step: Home template

This blog is going to be really simple and we only need an index. My idea behind this is just to fetch the article's summary from the API and then show these in our home with a link to dev.to.

I've created a quick wireframe using [Wireframe](https://wireframe.cc) as reference:

![Article wireframe](https://thepracticaldev.s3.amazonaws.com/i/6fy9dwrdpxbcqgbbncqx.png)

So, let's start by doing the template for our articles:

```html
//src/routes/index.svelte

<a href="/">
  Oct 10
  <h1>Svelte Create the Blog</h1>

  <div class="tags">
    <span class="tag">#svelte</span>
    <span class="tag">#javascript</span>
  </div>

  <p>How to create a blog using dev.to API and svelte</p>

  <img
    alt="Reactions"
    src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/reactions-stack-ee166e138ca182a567f74c986b6f810f670f4d199aca9c550cc7e6f49f34bd33.png" />
  <span>10</span>

  <img
    alt="Comments"
    src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/comments-bubble-9958d41b969a1620c614347d5ad3f270ab49582c1d9f82b617a6b4156d05dda0.png" />
  <span>3</span>

</a>

```

You will notice that is not the same as our wireframe. Let's add some CSS to our template.

```html
<style>
  a {
    text-decoration: none;
  }
  .card {
    padding: 20px;
    margin-bottom: 10px;
    border: 2px solid #bababa;
    box-shadow: 3px 3px 0px #bababa;
  }
  .tags {
    margin: 10px 0;
  }
  .tag {
    border-radius: 100px;
    background-color: #eaeaea;
    padding: 2px 8px;
    margin-right: 10px;
  }

  @media (max-width: 640px) {
    .tag {
      margin-right: 5px;
      font-size: 0.6rem;
    }
  }
  .article-engagement {
    margin-right: 20px;
  }
  .article-engagement img {
    height: 20px;
    min-width: 26px;
    vertical-align: -5px;
  }
  .flex-container {
    display: flex;
  }
</style>

<a href="/">
  <div class="card">
    Nov 24
    <h1>Create a blog with Svelte and DEV.to API</h1>

    <div class="tags">
      <span class="tag">#svelte</span>
      <span class="tag">#javascript</span>
      <span class="tag">#tutorial</span>
      <span class="tag">#blog</span>
    </div>

    <p>How to create a blog using dev.to API and svelte</p>

    <div class="flex-container">
      <div class="article-engagement">
        <img
          alt="Reactions"
          src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/reactions-stack-ee166e138ca182a567f74c986b6f810f670f4d199aca9c550cc7e6f49f34bd33.png" />
        <span>10</span>
      </div>

      <div class="article-engagement">
        <img
          alt="Comments"
          src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/comments-bubble-9958d41b969a1620c614347d5ad3f270ab49582c1d9f82b617a6b4156d05dda0.png" />
        <span>3</span>
      </div>

    </div>
  </div>
</a>


```

Ok, I think now is better.

![Article template](https://thepracticaldev.s3.amazonaws.com/i/7cci15x2htcc1sbyr8dv.png)


### Fourth step: Get articles from the API

Let's use DEV.to API for fetching all our articles, this way we are going to publish our posts in DEV.to platform and automatically is going to be shown in our blog :muscle: :sunglasses:.

We could find documentation on how to use the API here: [Docs API](https://docs.dev.to/api/#operation/getArticles) 

For summarize, if we only want to fetch articles we could just do a GET request without any extra authentication or step:

```bash
curl https://dev.to/api/articles?username=jpblancodb
```

The API response is:

```json
[
  {
    "type_of": "article",
    "id": 194541,
    "title": "There's a new DEV theme in town for all you 10x hackers out there (plus one actually useful new feature)",
    "description": "",
    "cover_image": "https://res.cloudinary.com/practicaldev/image/fetch/s--74Bl23tz--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://res.cloudinary.com/practicaldev/image/fetch/s--xU8cbIK4--/c_imagga_scale%2Cf_auto%2Cfl_progressive%2Ch_420%2Cq_auto%2Cw_1000/https://thepracticaldev.s3.amazonaws.com/i/8a39dzf3oovzc2snl7iv.png",
    "readable_publish_date": "Oct 24",
    "social_image": "https://res.cloudinary.com/practicaldev/image/fetch/s--SeMxdKIa--/c_imagga_scale,f_auto,fl_progressive,h_500,q_auto,w_1000/https://res.cloudinary.com/practicaldev/image/fetch/s--xU8cbIK4--/c_imagga_scale%2Cf_auto%2Cfl_progressive%2Ch_420%2Cq_auto%2Cw_1000/https://thepracticaldev.s3.amazonaws.com/i/8a39dzf3oovzc2snl7iv.png",
    "tag_list": [
      "meta",
      "changelog",
      "css",
      "ux"
    ],
    "tags": "meta, changelog, css, ux",
    "slug": "there-s-a-new-dev-theme-in-town-for-all-you-10x-hackers-out-there-plus-one-actually-useful-new-feature-2kgk",
    "path": "/devteam/there-s-a-new-dev-theme-in-town-for-all-you-10x-hackers-out-there-plus-one-actually-useful-new-feature-2kgk",
    "url": "https://dev.to/devteam/there-s-a-new-dev-theme-in-town-for-all-you-10x-hackers-out-there-plus-one-actually-useful-new-feature-2kgk",
    "canonical_url": "https://dev.to/devteam/there-s-a-new-dev-theme-in-town-for-all-you-10x-hackers-out-there-plus-one-actually-useful-new-feature-2kgk",
    "comments_count": 37,
    "positive_reactions_count": 142,
    "collection_id": null,
    "created_at": "2019-10-24T13:41:29Z",
    "edited_at": "2019-10-24T13:56:35Z",
    "crossposted_at": null,
    "published_at": "2019-10-24T13:52:17Z",
    "last_comment_at": "2019-10-25T08:12:43Z",
    "published_timestamp": "2019-10-24T13:52:17Z",
    "user": {
      "name": "Ben Halpern",
      "username": "ben",
      "twitter_username": "bendhalpern",
      "github_username": "benhalpern",
      "website_url": "http://benhalpern.com",
      "profile_image": "https://res.cloudinary.com/practicaldev/image/fetch/s--Y1sq1tFG--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/1/f451a206-11c8-4e3d-8936-143d0a7e65bb.png",
      "profile_image_90": "https://res.cloudinary.com/practicaldev/image/fetch/s--DcW51A6v--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/1/f451a206-11c8-4e3d-8936-143d0a7e65bb.png"
    },
    "organization": {
      "name": "The DEV Team",
      "username": "devteam",
      "slug": "devteam",
      "profile_image": "https://res.cloudinary.com/practicaldev/image/fetch/s--0kDBq1Ne--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://thepracticaldev.s3.amazonaws.com/uploads/organization/profile_image/1/0213bbaa-d5a1-4d25-9e7a-10c30b455af0.png",
      "profile_image_90": "https://res.cloudinary.com/practicaldev/image/fetch/s--8tTU-XkZ--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://thepracticaldev.s3.amazonaws.com/uploads/organization/profile_image/1/0213bbaa-d5a1-4d25-9e7a-10c30b455af0.png"
    }
  }
]
```

Let's add this API call into our `index.svelte` to fetch all our articles by adding a `script` tag:

```html
<script context="module">
  export async function preload() {
    const res = await this.fetch(
      `https://dev.to/api/articles?username=jpblancodb`
    );
    return { articles: await res.json() };
  }
</script>

<script>
  export let articles;
</script>
```
We are using `preload`, this loads data before the component gets rendered, then we return `{ articles }` and this is going to be passed as a prop to the component `export let articles;`

More about preload: [Sapper preload](https://sapper.svelte.dev/docs#Context)

From the response of the API we are only going to use the following properties:

```json
[
  {
    "title": "There's a new DEV theme in town for all you 10x hackers out there (plus one actually useful new feature)",
    "description": "",
    "readable_publish_date": "Oct 24",
    "tag_list": [
      "meta",
      "changelog",
      "css",
      "ux"
    ],
    "canonical_url": "https://dev.to/devteam/there-s-a-new-dev-theme-in-town-for-all-you-10x-hackers-out-there-plus-one-actually-useful-new-feature-2kgk",
    "comments_count": 37,
    "positive_reactions_count": 142
  }
]
```

Finally, we need to make our template dynamic. 

```html
<script context="module">
  export async function preload() {
    const res = await this.fetch(
      `https://dev.to/api/articles?username=jpblancodb`
    );
    return { articles: await res.json() };
  }
</script>

<script>
  export let articles;
</script>

<style>
  a {
    text-decoration: none;
  }
  .card {
    padding: 20px;
    margin-bottom: 10px;
    border: 2px solid #bababa;
    box-shadow: 3px 3px 0px #bababa;
  }
  .tags {
    margin: 10px 0;
  }
  .tag {
    border-radius: 100px;
    background-color: #eaeaea;
    padding: 2px 8px;
    margin-right: 10px;
  }
  .article-engagement {
    margin-right: 20px;
  }
  .article-engagement img {
    height: 20px;
    min-width: 26px;
    vertical-align: -5px;
  }
  @media (max-width: 640px) {
    .tag {
      margin-right: 5px;
      font-size: 0.6rem;
    }
  }
</style>

{#each articles as article}
  <a href={article.canonical_url}>
    <div class="card">
      {article.readable_publish_date}
      <h1>{article.title}</h1>

      <div class="tags">
        {#each article.tag_list as tag}
          <span class="tag">#{tag}</span>
        {/each}
      </div>

      <p>{article.description}</p>

      <div class="flex-container">
        <div class="article-engagement">
          <img
            alt="Reactions"
            src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/reactions-stack-ee166e138ca182a567f74c986b6f810f670f4d199aca9c550cc7e6f49f34bd33.png" />
          <span>{article.positive_reactions_count}</span>
        </div>

        <div class="article-engagement">
          <img
            alt="Comments"
            src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/comments-bubble-9958d41b969a1620c614347d5ad3f270ab49582c1d9f82b617a6b4156d05dda0.png" />
          <span>{article.comments_count}</span>
        </div>

      </div>
    </div>
  </a>
{/each}

```

Let's modify our `global.css` for customizing our styles;

```css
body {
  margin: 0;
  font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  color: #333;
}
h1 {
  margin: 0;
  font-weight: 400;
  line-height: 1.2;
  font-size: 2rem;
}
p {
  font-size: 1.2rem;
}
a {
  color: inherit;
  text-decoration: none;
}
.flex-container {
  display: flex;
}
@media (max-width: 640px) {
  body {
    font-size: 14px;
  }
  h1 {
    font-size: 1.2rem;
    margin: 0;
  }
  p {
    font-size: 0.9rem;
    line-height: normal;
  }
}

```

### Fifth step: Adding a header to our template

For getting our blog ready, we could add a header with our name, description. As this is going to be static, you could just add whatever you prefer. Here is mine! If you want to share yours, you could do it in the comments section :smiley:!

```html
//src/components/Header.svelte
<style>
  header {
    padding: calc(1.4vw + 8px) 0px;
    position: relative;
    overflow: hidden;
    width: 72em;
    max-width: 92%;
    margin: 30px auto 20px;
    background: #fff;
    background: var(--theme-container-background, #fff);
    border-radius: 3px;
    border: 2px solid #551029;
    box-shadow: 5px 6px 0px #551029;
  }
  .profile {
    display: flex;
    align-items: center;
    margin: 0 20px;
  }
  .profile-image {
    background: #61122f;
    width: calc(12.2vw + 50px);
    height: calc(12.2vw + 50px);
    border-radius: 200px;
    max-width: 220px;
    max-height: 220px;
    border: 4px solid #61122f;
  }
  .icon-img {
    margin-right: 1em;
    width: 35px;
    height: 35px;
  }
  .profile-description {
    display: flex;
    justify-content: center;
    flex-direction: column;
  }
  .role {
    font-style: italic;
    font-size: 1.4rem;
    margin: 10px 0;
    color: #999999;
  }
  .social {
    margin-top: 25px;
    color: #61122f;
  }
  h1 {
    color: #61122f;
    font-weight: 600;
  }
  @media (max-width: 640px) {
    .role {
      font-size: 1rem;
    }
    .social {
      margin-top: 5px;
    }
    .icon-img {
      margin-right: 1em;
      width: 25px;
      height: 25px;
    }
  }
</style>

<header>
  <div class="flex-container">
    <div class="profile">
      <img
        class="profile-image"
        src="https://res.cloudinary.com/practicaldev/image/fetch/s--122RH5V_--/c_fill,f_auto,fl_progressive,h_320,q_auto,w_320/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/16453/b219d515-8ee9-4f1d-9541-e4e49ce8d066.png"
        alt="jpblancodb profile" />
    </div>
    <div class="profile-description">
      <h1>Juan Pablo Blanco</h1>
      <span class="role">Software Developer</span>
      <div class="social">
        <a href="https://twitter.com/jpblancodb" target="_blank">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 612 612"
            role="img"
            aria-labelledby="a3nwio0iehsz7gmmysvll02oakahlvob"
            class="icon-img">
            <title id="a3nwio0iehsz7gmmysvll02oakahlvob">Twitter logo</title>
            <path
              d="M612 116.258c-22.525 9.98-46.694 16.75-72.088 19.772
              25.93-15.527 45.777-40.155 55.184-69.41-24.322 14.378-51.17
              24.82-79.775 30.48-22.906-24.438-55.49-39.66-91.63-39.66-69.333
              0-125.55 56.218-125.55 125.514 0 9.828 1.11 19.427 3.25
              28.606-104.325-5.24-196.834-55.223-258.75-131.174-10.822
              18.51-16.98 40.078-16.98 63.1 0 43.56 22.182 81.994 55.836
              104.48-20.575-.688-39.926-6.348-56.867-15.756v1.568c0 60.806 43.29
              111.554 100.692 123.104-10.517 2.83-21.607 4.398-33.08 4.398-8.107
              0-15.947-.803-23.634-2.333 15.985 49.907 62.336 86.2 117.253
              87.194-42.946 33.655-97.098 53.656-155.915 53.656-10.134
              0-20.116-.612-29.944-1.72 55.568 35.68 121.537 56.484 192.44
              56.484 230.947 0 357.187-191.29 357.187-357.188l-.42-16.253C573.87
              163.525 595.21 141.42 612 116.257z" />
          </svg>
        </a>
        <a href="https://github.com/jpblancodb" target="_blank">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="438.549"
            height="438.549"
            viewBox="0 0 438.549 438.549"
            role="img"
            aria-labelledby="a9umsuhtdvycvrsulea35mewfzunyupy"
            class="icon-img">
            <title id="a9umsuhtdvycvrsulea35mewfzunyupy">GitHub logo</title>
            <path
              d="M409.132
              114.573c-19.608-33.596-46.205-60.194-79.798-79.8C295.736 15.166
              259.057 5.365 219.27 5.365c-39.78 0-76.47 9.804-110.062
              29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0
              184.853 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164
              63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.42-1.996
              2.474-2.282 3.71-5.14 3.71-8.562
              0-.57-.05-5.708-.144-15.417-.098-9.71-.144-18.18-.144-25.406l-6.567
              1.136c-4.187.767-9.47 1.092-15.846
              1-6.375-.09-12.992-.757-19.843-2-6.854-1.23-13.23-4.085-19.13-8.558-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.9-9.233-8.992-14.56-4.093-5.33-8.232-8.944-12.42-10.847l-1.998-1.43c-1.332-.952-2.568-2.1-3.71-3.43-1.143-1.33-1.998-2.663-2.57-3.997-.57-1.335-.097-2.43
              1.428-3.29 1.525-.858 4.28-1.275 8.28-1.275l5.708.853c3.807.763
              8.516 3.042 14.133 6.85 5.615 3.807 10.23 8.755 13.847 14.843 4.38
              7.807 9.657 13.755 15.846 17.848 6.184 4.093 12.42 6.136 18.7
              6.136 6.28 0 11.703-.476 16.273-1.423 4.565-.95 8.848-2.382
              12.847-4.284 1.713-12.758 6.377-22.56
              13.988-29.41-10.847-1.14-20.6-2.857-29.263-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.98-3.9-12.373-5.852-26.647-5.852-42.825
              0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.38-36.732
              1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.284 18.794
              7.953 23.84 10.995 5.046 3.04 9.09 5.618 12.135 7.708 17.706-4.947
              35.977-7.42 54.82-7.42s37.116 2.473 54.822
              7.42l10.85-6.85c7.418-4.57 16.18-8.757 26.26-12.564 10.09-3.806
              17.803-4.854 23.135-3.14 8.562 21.51 9.325 40.923 2.28 58.24
              15.035 16.18 22.558 35.788 22.558 58.818 0 16.178-1.958
              30.497-5.853 42.966-3.9 12.47-8.94 22.457-15.125 29.98-6.19
              7.52-13.9 13.85-23.13 18.985-9.233 5.14-18.183 8.85-26.84
              11.135-8.663 2.286-18.416 4.004-29.264 5.146 9.894 8.563 14.842
              22.078 14.842 40.54v60.237c0 3.422 1.19 6.28 3.572 8.562 2.38
              2.278 6.136 2.95 11.276 1.994 44.163-14.653 80.185-41.062
              108.068-79.226 27.88-38.16 41.826-81.126
              41.826-128.906-.01-39.77-9.818-76.454-29.414-110.05z" />
          </svg>
        </a>
        <a href="https://dev.to/jpblancodb">
          <img
            class="icon-img"
            src="https://d2fltix0v2e0sb.cloudfront.net/dev-badge.svg"
            alt="JPBlancoDB's DEV Profile" />
        </a>
      </div>
    </div>
  </div>
</header>

```

We need to create a `Header.svelte` component in `src/components` and then we should import it into our `src/routes/_layout.svelte`

```html
<script>
  import Header from "../components/Header.svelte";
</script>

<style>
  main {
    position: relative;
    max-width: 56em;
    background-color: white;
    padding: 2em;
    margin: 0 auto;
    box-sizing: border-box;
  }
</style>

<svelte:head>
  <title>Juan Pablo Blanco - Blog</title>
</svelte:head>

<Header />

<main>
  <slot />
</main>

```

### Bonus: Error template

As bonus track (and optional), when any error occurs let's show our Borat image with the error message:

```javascript
//src/routes/_error.svelte
<script>
  export let status;
  export let error;

  const dev = process.env.NODE_ENV === "development";
</script>

<style>
  h1 {
    font-size: 2.8em;
    font-weight: 700;
    margin: 0 0 0.5em 0;
  }

  @media (min-width: 480px) {
    h1 {
      font-size: 4em;
    }
  }
</style>

<h1>{status}</h1>

<figure>
  <img alt="Borat" src="great-success.png" />
  <figcaption>{error.message}</figcaption>
</figure>

{#if dev && error.stack}
  <pre>{error.stack}</pre>
{/if}

``` 

For checking this blog running, you could visit my blog: [JPBlancoDB](https://jpblanco.dev/)

If you have any question, you could leave a comment or ask me via [Twitter](https://twitter.com/jpblancodb)! I'm happy to help.


ps: I'm using [now](https://zeit.co] and for reference on deploying with v2 [Sapper Now v2 demo](https://github.com/beyonk-adventures/now-sapper-demo)