Create a simple to do app using Svelte and GraphQL

Let’s create a simple ‘to do’ application using Svelte and GraphQL. The objective is to demonstrate getting started on a GraphQL query and insert operation in a Svelte component.

If you are new to Svelte, you may want to go over what Svelte can do from the official site, or see Svelte quick overview.

Also, we will not quite dwell on the backend. You can follow along if you have a GraphQL backend. If not, I suggest you get started with a GraphQL setup of your own in the next 15 min. I will use the data structure and statements from the server setup described in the post.

I gather you would have installed degit to make your svelte life easier. Create a new project.

degit sveltejs/template svedo
cd svedo
npm i

This will copy svelte template and install all required components. Open the project folder in VS Code.

We will also install Apollo client for GraphQL. Go back to command window and do another npm.

npm i --save apollo-boost graphql svelte-apollo

Run your application and say ‘hello’ to the world.

npm run dev

Starting changing stuff.

main.js

Change your main.js for fun.

import App from "./App.svelte";

const app = new App({
  target: document.body,
  props: {
    name: "Mr. Anderson"
  }
});

export default app;

Don’t know a Mr. Anderson? Please stop doing Svelte, see at least a 100 sci-fi movies and come back. The world will wait for you.

App.svelte

Open App.svelte. There are two main things we need to do here -

  1. Point Apollo client towards our GraphQL server URL
  2. Create a Todo Svelte component (we will create this next)

Also, centre the whole thing. Never trust developers who don’t put content at the centre.

<script>
  import ApolloClient from "apollo-boost";
  import { setClient } from "svelte-apollo";
  import Todo from "./Todo.svelte";

  export let name;

  const client = new ApolloClient({
    uri: "http://localhost:5050/graphql",

    onError: ({ networkError, graphQLErrors }) => {
      console.log("graphQLErrors", graphQLErrors);
      console.log("networkError", networkError);
    }
  });

  setClient(client);
</script>

<style>
  h1 {
    color: purple;
  }

  h5 {
    color: grey;
  }

  .centrify {
    text-align: center;
  }
</style>

<div class="centrify">
  <h5>Hello {name}!</h5>
  <Todo />
</div>

You might remember from the server setup post that I linked earlier that we are running GraphQL server at port 5050. Other stuff here is not that important, and largely self explanatory.

Todo.svelte

We will do most of the changes here. Create a new file Todo.svelte in the same directory as App.svelte.

First: write the GraphQL queries - one for query and one for insert, and get the client ready.

<script>
  import { getClient, query, mutate } from "svelte-apollo";
  import { gql } from "apollo-boost";

  const GETTODO = gql`
    {
      allTodos {
        nodes {
          id
          title
          done
        }
      }
    }
  `;

  const ADDTODO = gql`
    mutation($todoEdit: String!) {
      createTodo(input: { todo: { title: $todoEdit, done: false } }) {
        todo {
          id
          title
          done
        }
      }
    }
  `;
</script>

Next: add functionality to fetch to-do from server. You can incrementally add code to perform query and update operations within the <script> block.

const client = getClient();
const todoOp = query(client, { query: GETTODO });

Add a function to insert a to-do record, and declare a variable which will hold the to-do value temporarily.

let todoEdit = "";
function addTodo() {
  const todoAdd = mutate(client, {
    mutation: ADDTODO,
    variables: {
      todoEdit
    }
  })
    .then(data => {
      todoEdit = "";
      todoOp.refetch();
    })
    .catch(e => {
      console.error("error: ", e);
    });
}

Typically we would want to just take the input, add to the list of to-dos, and call it a day. Here, we are ’re-fetching’ the results after insert just because we can. Remember that PostGraphile provides the fields we specified post insert, however other technologies may not. This is where refetch has a role to play. Depending on your use case, you may or may not want to throw a re-fetch query at server.

Anyways, back to the app. Now, add the HTML to the same Todo.svelte file to render the results fetched from server -

<div style="text-align:center">
  <h2>Svedos</h2>

  {#await $todoOp}

  <p>.. loading</p>

  {:then data}
    {#each data.data['allTodos']['nodes'] as todo, i}
    <p class:done="{todo.done}">{todo.title}</p>
    {/each}
  {:catch e}
    {e}
  {/await}
</div>

We do the following tasks in the above block -

  1. Wait for the fetch operation from the function. Remember that the function gets executed when the component gets loaded. The Todo.svelte component loads when called by App.svelte, which is itself called by main.js
  2. Till the data is fetched, a helpful message ...loading is displayed
  3. Once data is fetched, we use the standard GraphQL node traversal to fetch nodes from result, and display them on the page using a {#each} loop.
  4. We take care to catch any exceptions - just in case

By now, you should see the to-do records fetched and displayed on the page.

Now, add a form element before or after the list displayed in the previous section. This comprises of just a single input box and a submit button.

<form on:submit|preventDefault="{addTodo}">
  <input placeholder="new todo" bind:value="{todoEdit}" />
  <button method="submit">Submit</button>
</form>

Bind the input box to the (temporary) to-do variable we had declared earlier. We prevent default action on button click so that the page does not go into wild, unnecessary refresh’es.

And, there it is - your beautiful looking app that can insert or query to-dos’.

Insert to-do, fetch your existing to-dos - what more can a human ask for?

Full Code for Todo.svelte

Copy/paste was never made more easier (exception exists in the immediate next section).

<script>
  import { getClient, query, mutate } from "svelte-apollo";
  import { gql } from "apollo-boost";

  const GETTODO = gql`
    {
      allTodos {
        nodes {
          id
          title
          done
        }
      }
    }
  `;

  const ADDTODO = gql`
    mutation($todoEdit: String!) {
      createTodo(input: { todo: { title: $todoEdit, done: false } }) {
        todo {
          id
          title
          done
        }
      }
    }
  `;

  let todoEdit = "";
  const client = getClient();
  const todoOp = query(client, { query: GETTODO });

  function addTodo() {
    const todoAdd = mutate(client, {
      mutation: ADDTODO,
      variables: {
        todoEdit
      }
    })
      .then(data => {
        todoEdit = "";
        todoOp.refetch();
      })
      .catch(e => {
        console.error("error: ", e);
      });
  }
</script>

<style>
  .done {
    text-decoration: line-through;
  }
</style>

<div style="text-align:center">
  <h2>Svedos</h2>
  <form on:submit|preventDefault="{addTodo}">
    <input placeholder="new todo" bind:value="{todoEdit}" />
    <button method="submit">Submit</button>
  </form>

  {#await $todoOp}

  <p>.. loading</p>

  {:then data} {#each data.data['allTodos']['nodes'] as todo, i}
  <p class:done="{todo.done}">{todo.title}</p>
  {/each} {:catch e} {e} {/await}
</div>

Final final code

Find all code on GitHub

comments powered by Disqus