How to Create a Blog with SvelteKit and Strapi
Tags: sveltekit, strapi, javascript
Published: 2021-07-07 15:23:37

[SvelteKit](https://kit.svelte.dev) is a relatively new SSR framework for SvelteJS. We can compare it to NextJS, but instead of using React, it uses Svelte. In this tutorial, you'll learn how you can create a blog website with SvelteKit and Strapi as a CMS.

**What is a CMS?**

A CMS, or a Content Management System, is a popular tool for creating web pages, blogs, and online stores. They store your website's data, images, pictures, and other hosted content. They are popular among bloggers because anyone can spin up a blog pretty quickly.

[Strapi](https://strapi.io) is a Headless CMS coded in Javascript. A headless CMS has no frontend, only an admin panel, so it is ideal for developers. In other words, a Headless CMS is an API that serves up your content to be consumed by a frontend.

## Goals for today

In this tutorial, you'll code a blog website using the [SvelteKit](https://kit.svelte.dev) framework. You'll use [Strapi](https://strapi.io) for content management.

You'll learn the basics of SvelteKit, including:

- Filesystem based routing
- Preloading content
- Dynamic routing
- 404 Error handling
- SvelteKit Layouts
- And much more

You'll also learn how to use Strapi to manage your content.

## Prerequisites
- [NodeJS](https://nodejs.org) and [NPM](https://npmjs.com) installed on your machine.
- Javascript and [Svelte](https://svelte.dev) knowledge. (Svelte has [a great tutorial](https://svelte.dev/tutorial), so go check it out!)
- Some coffee and articles to write on your new blog!


## Creating a Strapi project


Now that you know what a CMS and Strapi are, you can get started by locally hosting Strapi, or in other words, Creating a Strapi Project.

Run this command in an empty folder to create a Strapi project.

```
    npx create-strapi-project cms --quickstart
```


You can replace the word `cms` with anything you like. This command will be the name of the folder your new Strapi project will sit in.

Now sit back, relax, and wait for the project to create itself. After that, you should automatically be taken to localhost:1377. 

If that's not the case, `cd` into the newly created folder and run this command.

```
    npm run strapi develop
```


This command will start Strapi up on port `1337`. If a page is not automatically opened up for you, open [localhost:1377](http://localhost:1377) to launch the Strapi Admin Page.

It should present you with this page. You need to sign up for an account here. **Remember that this account is locally-scoped to this project, meaning it won't work on other Strapi projects.**


![The Strapi Admin login page](https://i.imgur.com/bQFuopK.png)


**Creating content types**

In the sidebar of Strapi admin `http://localhost:1377`, click the `Content-Types Builder` button. This button should take you to the Content Types builder page.


![The content builder page](https://i.imgur.com/7OtSRoU.png)

## What are content types?

We can compare a content type to a table in SQL. Thus, content types help us structure and categorize our data.

**Posts**

Let's create a content type for posts. Then, click the `Create new Collection Type` button to launch a dialogue.

Enter `Post` as the name. Go to `Advanced Settings` at the top and disable the Drafts system. Click Continue

![The Content Type Creation Dialogue](https://i.imgur.com/7YzVrrs.png)


Add these fields. Refer to the image if you get stuck.

- A `title` field with type `Text`.
- A `description` field with type `Text`. Make sure the text is `Long text`.
- A `content` field with the `Rich Text` type.


![The Post content type's fields](https://i.imgur.com/qmGgLFt.png)


Let's also add a relation between `Post` and User from the `users-permissions` plugin. This relationship allows us to easily link a post to a user to display relevant information like the Author's name and profile picture, fetch the Author's posts, etc.

Add a new `Relation` field to the `Post` content type. The column on the left should be PostPost, and the column on the right should be User from `users-permissions`. Select the fourth relation; the `User` has many `Posts` and clicks `Finish`. 

Refer to the below image if you get stuck:


![Post and User relation dialogue](https://i.imgur.com/7pnHCe1.png)


Click `Save` to save your changes and restart the server.

**Setting up roles and permissions**

If you try querying the [Content API](https://strapi.io/documentation/developer-docs/latest/developer-resources/content-api/content-api.html#api-endpoints) for the `posts` content type, i.e., at `http://localhost:5000/posts`, you'll get a `403 FORBIDDEN` error.

This error is because, by default, Strapi doesn't allow any man down the street to access your content. You're supposed to set rules and permissions for specific roles. Let's allow the `Public` role, i.e., an unauthenticated user, to read our posts.

In the sidebar, click on the `Settings` button. There, click on `Roles` in the `Users & Permissions` section. But, first, let's edit the permissions for the Public role.
We'll allow the `Public` role to `count`, `find` and `findOne` for Posts.


![Roles Settings Page for the Public Role](https://i.imgur.com/sCyu7CJ.png)


Next, let's do the same for the `Authenticated` role, but we'll also allow them to create, update and delete posts as well.


![The settings for the Authenticated Role](https://i.imgur.com/39jLkby.png)

## Creating a SvelteKit project

Now for the main [SvelteKit](https://kit.svelte.dev) code. Create a new folder named `frontend` in the same directory as the folder for the Strapi project and `cd` into it. 

Now, let's create a SvelteKit project with this command:

```
    npm init svelte@next
```

Be sure to choose the options as shown in the below image.

![The output of the above command](https://i.imgur.com/qVNyCXu.png)


Launch the app inside your favourite editor, for example, [VSCode](https://code.visualstudio.com). Now, we can start the app with the below two commands:

```

    # To install packages
    npm i 
    
    # To start the app
    npm run dev
```

> Feel free to use `[yarn](https://yarnpkg.org)` instead of `[npm](https://npmjs.com)`

Here's how your app should look, hosted on [localhost:3000](https://localhost:3000)

![Your SvelteKit app](https://i.imgur.com/aXLfKrI.png)


**Install** [**TailwindCSS**](https://tailwindcss.com)

[TailwindCSS](https://tailwindcss.com) is a straightforward way to style your apps, and it's clear to add them to SvelteKit.
We'll use [SvelteAdd](https://github.com/svelte-add/svelte-add) to add TailwindCSS to our application quickly. 

Run the below command to add TailwindCSS to our project.

```
    npx svelte-add tailwindcss
```


Be sure to stop the running server with `Ctrl+C` first.

Let's start our project from scratch. 

Delete all the extra CSS, except the `@tailwind` parts in `src/app.postcss`.  Delete all of the items in `src/routes` and `src/lib`, and now we should be left with an empty project.

Create a new file `src/routes/index.svelte`. All files in the `src/routes` folder will be mapped to actual routes.  For example, `src/routes/example.svelte` will be accessible at `/example`, and `src/routes/blog/test.svelte` will be accessible at `/blog/test`.  `index.svelte` is a special file. It maps to the base directory. `src/routes/index.svelte` maps to `/`, and `src/routes/blog/index.svelte` maps to `/blog`.

This is how filesystem based routing works in SvelteKit. Later, you'll learn to add routes with dynamic names.

For now, let's work on the basic UI. Add the following code to `index.svelte`

```
    <script lang="ts">
    </script>
    
    <div class="my-4">
        <h1 class="text-center text-3xl font-bold">My wonderful blog</h1>
    </div>
```

> I've elected to use [Typescript](https://www.typescriptlang.org/), which is just like Javascript, but with types. You can follow along with javascript, but types and interfaces won't work for you.
> Also, you shouldn't put `lang="ts"` in your `script` tag.

Now you'll notice that TailwindCSS is no longer working. This is because we deleted `__layout.svelte`, the file importing `src/app.postcss`. Let's now learn what this `__layout.svelte` file is.

`__layout.svelte` is a special file that adds a layout to every page. `__layout.svelte`s can not only exist at the top level `routes` folder, but can also exist in subdirectories, applying layouts for that subdirectory.

Read more about [SvelteKit layouts here](https://kit.svelte.dev/docs#layouts).

For now, all we have to do, is create `src/routes/__layout.svelte` and import `src/app.postcss` in it.

```
    <script lang="ts">
        import '../app.postcss';
    </script>
    
    <slot />
```

> The `<slot />` element will be the actual content displayed on the page.


Now here, in this `__layout.svelte` file, we can add whatever content we want, and it'll be displayed on all pages. So, add your Navbars, Headers, Footers, and everything else here.

## Fetch blog posts

Now, we can fetch blog posts from Strapi and display them in `index.svelte`. We'll utilize [SvelteKit Endpoints](https://kit.svelte.dev/docs#routing-endpoints) to make API fetching easier. Endpoints in SvelteKit are files ending with `.js` (or `.ts` for typescript) that export functions corresponding to HTTP methods. These endpoint files become API routes in our application.

Let's create an endpoint `src/routes/posts.ts` (use the `.js` extension if you're not using typescript)

```
    // src/routes/posts.ts
    
    import type { EndpointOutput } from '@sveltejs/kit';
    
    export async function get(): Promise<EndpointOutput> {
        const res = await fetch('http://localhost:1337/posts');
        const data = await res.json();
    
        return { body: data };
    }
```

> Ignore the typings if you're using `javascript`.

Now, when we visit `http://localhost:3000/posts`, we'll receive the posts from Strapi. Let's implement this route in our `index.svelte` file using [SvelteKit's Loading functionality](https://kit.svelte.dev/docs#loading). Loading allows us to fetch APIs before the page is loaded using a particular `<script context=" module">` tag.

Add this to the top of `src/routes/index.svelte`.

```

    <script lang="ts" context="module">
        import type { Load } from '@sveltejs/kit';
    
        export const load: Load = async ({ fetch }) => {
            const res = await fetch('/posts');
            const data = await res.json();
    
            return { props: { posts: data } };
        };
    </script>
    
    <script lang="ts">
        export let posts: any;
    </script>

```


You can see that the `load` function takes in the `fetch` function provided to us by SvelteKit and returns an object containing `props`. These props are passed down to our components.

```

    <!-- src/routes/index.svelte -->
    <script lang="ts" context="module">
        import type { Load } from '@sveltejs/kit';
    
        export const load: Load = async ({ fetch }) => {
            const res = await fetch('/posts');
            const data = await res.json();
    
            return { props: { posts: data } };
        };
    </script>
    
    <script lang="ts">
        import type { Post } from '$lib/types';
      import { goto } from "$app/navigation"
    
        export let posts: Post[];
    </script>
    
    <div class="my-4">
        <h1 class="text-center text-3xl font-bold">My wonderful blog</h1>
    </div>
    
    <div class="container mx-auto mt-4">
        {#each posts as post}
            <div class="hover:bg-gray-200 cursor-pointer px-6 py-2 border-b border-gray-500" on:click={() => goto("/blog/" + post.id)}>
                <h4 class="font-bold">{post.title}</h4>
                <p class="mt-2 text-gray-800">{post.description}</p>
                <p class="text-gray-500">By: {post.author.username}</p>
            </div>
        {/each}
    </div>
```


> I've added a few typings in `src/lib/types.ts`. You can check it out in the [Source Code](https://github.com/arnu515/sveltekit-strapi-blog)
> SvelteKit allows us to access any file in `src/lib` using the `[$lib](https://kit.svelte.dev/docs#modules-$lib)` alias.

I added a test user and a test post in Strapi, and this is how my app looks.

![A screenshot of the app so far](https://i.imgur.com/vwsIysa.png)


**Posts page**

Now, let's add a route that'll allow us to view a post. Now, you'll learn about [Dynamic Routes in SvelteKit](https://kit.svelte.dev/docs#routing-pages).

If we enclose a string in brackets `([])` in a filename of a route, that becomes a parameter. So, for example, if I have a route called `src/routes/blog/[post].svelte`, the route maps to `/blog/ANY_STRING` where `ANY_STRING` will be the value of the `post` parameter. Let's use this to query posts with Strapi.

We can use the `load` function we talked about earlier to get the parameters. Create a file called `src/routes/blog/[slug].svelte` and add the below code to it.

```

    <!-- src/routes/blog/[slug].svelte -->
    <script lang="ts" context="module">
        import type { Load } from '@sveltejs/kit';
    
        export const load: Load = async ({ page: { params }, fetch }) => {
            // The params object will contain all of the parameters in the route.
            const { slug } = params;
    
            // Now, we'll fetch the blog post from Strapi
            const res = await fetch('http://localhost:1337/posts/' + slug);
    
            // A 404 status means "NOT FOUND"
            if (res.status === 404) {
                // We can create a custom error and return it.
                // SvelteKit will automatically show us an error page that we'll learn to customise later on.
                const error = new Error(`The post with ID ${slug} was not found`);
                return { status: 404, error };
            } else {
                const data = await res.json();
                return { props: { post: data } };
            }
        };
    </script>
    
    <script lang="ts">
        import type { Post } from '$lib/types';
        import { onMount } from 'svelte';
    
        export let post: Post;
        let content = post.content;
    
        onMount(async () => {
            // Install the marked package first!
            // Run this command: npm i marked
    
            // We're using this style of importing because "marked" uses require, which won't work when we import it with SvelteKit.
            // Check the "How do I use a client-side only library" in the FAQ: https://kit.svelte.dev/faq
            const marked = (await import('marked')).default;
            content = marked(post.content);
        });
    </script>
    
    <h1 class="text-center text-4xl mt-4">{post.title}</h1>
    <p class="text-center mt-2">By: {post.author.username}</p>
    
    <div class="border border-gray-500 my-4 mx-8 p-6 rounded">
        {@html content}
    </div>
```

> We need to use the `@html` directive when we want the content to be actually rendered as HTML.

## Authentication and authorization

Let's get started with authenticating users to our blog. Strapi allows us to configure third-party providers like [Google](https://google.com), but we'll stick to the good ol' email and password sign-in.

We don't want anybody to register to our blog, so we'll manually create a user with Strapi. Then, in the Strapi admin panel, click on the `Users` collection type in the sidebar.

There, click `Add new Users` and create your user. Here's mine, for example.


![](https://paper-attachments.dropbox.com/s_EA96DA0DA80E614292943B12910B0B1DF88DA3403686B8316A5E36523C012B0D_1624968500099_kVM9gHV.png)


Click `Save` when done

We can test logging in to our user by sending a `POST` request to `http://localhost:5000/auth/local`. Follow the image below for the correct JSON body.


![Insomnia screenshot](https://i.imgur.com/bct4pi5.png)

> The REST client I'm using in the above image is [Insomnia](https://insomnia.rest)

**The login route**
Let's create a new route `src/routes/login.svelte`. This will of course map to `/login`.

```

    <script lang="ts">
        import type { User } from '$lib/types';
        import { goto } from '$app/navigation';
        import user from '$lib/user';
    
        let email = '';
        let password = '';
    
        async function login() {
            const res = await fetch('http://localhost:1337/auth/local', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
                body: JSON.stringify({ identifier: email, password })
            });
            if (res.ok) {
                const data: {user: User, jwt: string} = await res.json();
                localStorage.setItem("token", data.jwt)
                if (data) {
                    $user = data.user;
                    goto('/');
                }
            } else {
                const data: { message: { messages: { message: string }[] }[] } = await res.json();
                if (data?.message?.[0]?.messages?.[0]?.message) {
                    alert(data.message[0].messages[0].message);
                }
            }
        }
    </script>
    
    <form on:submit|preventDefault={login} class="container mx-auto my-4">
        <h1 class="text-center text-2xl font-bold">Login</h1>
    
        <div class="my-1">
            <label for="email">Email</label>
            <input type="email" placeholder="Enter your email" bind:value={email} />
        </div>
        <div class="my-1">
            <label for="password">Password</label>
            <input type="password" placeholder="Enter your password" bind:value={password} />
        </div>
        <div class="my-3">
            <button class="submit" type="submit">Login</button>
        </div>
    </form>
    
    <style lang="postcss">
        label {
            @apply font-bold block mb-1;
        }
    
        input {
            @apply bg-white w-full border border-gray-500 rounded outline-none py-2 px-4;
        }
    
        .submit {
            @apply bg-blue-500 text-white border-transparent rounded px-4 py-2;
        }
    </style>
```

I've created a store in `src/lib/user.ts` that will house the User to access the User in any component.

Here's the code:

```

    // src/lib/user.ts
    
    import { writable } from 'svelte/store';
    import type { User } from './types';
    
    const user = writable<User | null>(null);
    
    export default user;
```


**Persisting auth state**

Great! Our `/login` page works flawlessly, but there's one problem - When we refresh the page, the user store gets reset to `null`. To fix this, we need to re-fetch the User every time the page reloads. That's right, we need a `load` function in `__layout.svelte` since it is present on every page.

Change `__layout.svelte` to this code:

```

    <!-- src/routes/__layout.svelte -->
    <script lang="ts">
        import '../app.postcss';
        import userStore from '$lib/user';
        import type { User } from '$lib/types';
        import { onMount } from 'svelte';
    
        let loading = true;
    
        onMount(async () => {
            // Check if 'token' exists in localStorage
            if (!localStorage.getItem('token')) {
          loading = false;
          return { props: { user: null } };
        }
    
            // Fetch the user from strapi
            const res = await fetch('http://localhost:1337/auth/me', {
                headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
            });
            const user: User = await res.json();
            loading = false;
            if (res.ok) {
                $userStore = user;
            }
        });
    </script>
    
    {#if !loading}
        <slot />
    {/if}
```


Wait a minute! Why are we using `onMount` instead of `load`? Since `load` is executed on the server, we won't have access to `localStorage`, which is on the browser. Hence, we have to wait for the app to load before accessing `localStorage`.

If you visit your app, you'll get a `404` Error when trying to get the User from Strapi. This error is because `/auth/me` isn't a valid route. So let's create it ourselves.

Open the Strapi project in your favorite editor. Strapi allows us to add custom API routes to it. Let's use the `strapi` CLI to generate a route.

```
    npx strapi generate:controller Auth
```


You'll find a new file called `/api/auth/controllers/Auth.js`. We need to add our simple controller here.

```

    "use strict";
    
    /**
     * A set of functions called "actions" for `auth`
     */
    
    module.exports = {
      async me(ctx) {
        if (ctx.state.user) {
          return ctx.state.user;
        }
    
        ctx.unauthorized("You're not logged in");
      },
    };
```

This simple API route will return the User if it exists or give us a `401 UNAUTHORIZED` error. Now, we need to tell Strapi to register this controller at `/auth/me`. To do that, create file `/api/auth/config/routes.json`.

```

    {
      "routes": [
        {
          "method": "GET",
          "path": "/auth/me",
          "handler": "Auth.me",
          "config": {
            "policies": []
          }
        }
      ]
    }
```


Now, if we access `/auth/me`, we get `403 FORBIDDEN`. Like the `post` routes, Strapi doesn't, by default, allow anyone to access this route either. So, let's edit permissions like how we did earlier for the `Authenticated` role.


![Users and permissions roles editor](https://i.imgur.com/zmpcZyZ.png)


And now, everything should work flawlessly.

**Navbar**
Let's add a quick navbar to our app. Create file `src/lib/Navbar.svelte` and put the below code in it.

```

    <!-- src/lib/Navbar.svelte -->
    <script lang="ts">
        import user from './user';
    </script>
    
    <nav class="bg-white border-b border-gray-500 py-2 px-4 w-full">
        <div class="flex items-center justify-between container mx-auto">
            <a href="/" class="font-bold no-underline">My blog</a>
            <section>
                {#if !$user}
                    <a href="/login" class="font-mono no-underline">Login</a>
                {:else}
                    <a href="/new" class="font-mono no-underline mr-3">New</a>
                    <span class="font-mono text-gray-500">{$user.username}</span>
                {/if}
            </section>
        </div>
    </nav>
```

Add the Navbar to `__layout.svelte`

```

    <!-- src/routes/__layout.svelte -->
    <script lang="ts">
      // ...
      import Navbar from "$lib/Navbar.svelte";
    </script>
    
    <Navbar />
    <slot />
```

## Create and update posts

Now, let's get to the juicy part. Add a file called `src/routes/new.svelte`. This file will contain the form used to create a new post on Strapi.

```

    <!-- src/routes/new.svelte -->
    <script lang="ts" context="module">
        import type { Load } from '@sveltejs/kit';
        import type { Post } from '$lib/types';
    
        export const load: Load = async ({ fetch, page: { query } }) => {
            // edit will be an optional query string parameter that'll contain the ID of the post that needs to be updated.
            // If this is set, the post will be updated instead of being created.
            const edit = query.get('edit');
    
            if (edit) {
                const res = await fetch('http://localhost:1337/posts/' + edit);
    
                if (res.status === 404) {
                    const error = new Error(`The post with ID ${edit} was not found`);
                    return { status: 404, error };
                } else {
                    const data: Post = await res.json();
                    return {
                        props: {
                            editId: edit,
                            title: data.title,
                            content: data.content,
                            description: data.description
                        }
                    };
                }
            }
    
            return { props: {} };
        };
    </script>
    
    <script lang="ts">
        import { onMount } from 'svelte';
        import user from '$lib/user';
        import { goto } from '$app/navigation';
    
        export let editId: string;
        export let title = '';
        export let description = '';
        export let content = '';
    
        onMount(() => {
            if (!$user) goto('/login');
        });
    
        // To edit the post
        async function editPost() {
            if (!localStorage.getItem('token')) {
                goto('/login');
                return;
            }
    
            const res = await fetch('http://localhost:1337/posts/' + editId, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
                    Accept: 'application/json',
                    Authorization: 'Bearer ' + localStorage.getItem('token')
                },
                body: JSON.stringify({ title, description, content })
            });
            if (!res.ok) {
                const data: { message: { messages: { message: string }[] }[] } = await res.json();
                if (data?.message?.[0]?.messages?.[0]?.message) {
                    alert(data.message[0].messages[0].message);
                }
            } else {
                const data: Post = await res.json();
                goto('/blog/' + data.id);
            }
        }
    
        async function createPost() {
            if (!localStorage.getItem('token')) {
                goto('/login');
                return;
            }
    
            if (editId) {
                // We're supposed to edit, not create
                editPost();
                return;
            }
    
            const res = await fetch('http://localhost:1337/posts', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Accept: 'application/json',
                    Authorization: 'Bearer ' + localStorage.getItem('token')
                },
                body: JSON.stringify({ title, description, content })
            });
            if (!res.ok) {
                const data: { message: { messages: { message: string }[] }[] } = await res.json();
                if (data?.message?.[0]?.messages?.[0]?.message) {
                    alert(data.message[0].messages[0].message);
                }
            } else {
                const data: Post = await res.json();
                goto('/blog/' + data.id);
            }
        }
    </script>
    
    <form on:submit|preventDefault={createPost} class="my-4 mx-auto container p-4">
        <div class="my-1">
            <label for="title">Title</label>
            <input type="text" placeholder="Enter title" id="title" bind:value={title} />
        </div>
        <div class="my-1">
            <label for="description">Description</label>
            <input type="text" placeholder="Enter description" id="description" bind:value={description} />
        </div>
        <div class="my-1">
            <label for="title">Content</label>
            <textarea rows={5} placeholder="Enter content" id="content" bind:value={content} />
        </div>
        <div class="my-2">
            <button class="submit" type="submit">Submit</button>
        </div>
    </form>
    
    <style lang="postcss">
        label {
            @apply font-bold block mb-1;
        }
    
        input {
            @apply bg-white w-full border border-gray-500 rounded outline-none py-2 px-4;
        }
    
        textarea {
            @apply bg-white w-full border border-gray-500 rounded outline-none py-2 px-4 resize-y;
        }
    
        .submit {
            @apply bg-blue-500 text-white border-transparent rounded px-4 py-2;
        }
    </style>
```


Don't try this out yet, since there's currently no way to determine the Author of the PostPost. We need to code that in Strapi explicitly.

Let's create custom controllers for the `Post` content type. Here, we'll make it so that the Author of a post will be the currently logged-in User.

Edit `api/post/controllers/post.js` in the Strapi project.

```

    "use strict";
    
    const { parseMultipartData, sanitizeEntity } = require("strapi-utils");
    
    /**
     * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers)
     * to customize this controller
     */
    
    module.exports = {
      async create(ctx) {
        let entity;
    
        if (ctx.is("multipart")) {
          const { data, files } = parseMultipartData(ctx);
          data.author = ctx.state.user.id;
          entity = await strapi.services.post.create(data, { files });
        } else {
          ctx.request.body.author = ctx.state.user.id;
          entity = await strapi.services.post.create(ctx.request.body);
        }
    
        return sanitizeEntity(entity, { model: strapi.models.post });
      },
    
      async update(ctx) {
        const { id } = ctx.params;
    
        let entity;
    
        const [article] = await strapi.services.post.find({
          id: ctx.params.id,
          "author.id": ctx.state.user.id,
        });
    
        if (!article) {
          return ctx.unauthorized(`You can't update this entry`);
        }
    
        if (ctx.is("multipart")) {
          const { data, files } = parseMultipartData(ctx);
          entity = await strapi.services.post.update({ id }, data, {
            files,
          });
        } else {
          entity = await strapi.services.post.update({ id }, ctx.request.body);
        }
    
        return sanitizeEntity(entity, { model: strapi.models.post });
      },
    };
```


> If you get confused, checkout [this](https://strapi.io/documentation/developer-docs/latest/guides/is-owner.html#introduction)

And now, you should be able to create and update posts all from one route. Let's make the update process easier. Change `src/routes/blog/[slug].svelte` to the code below:

```

    <!-- src/routes/blog/[slug].svelte -->
    <script lang="ts" context="module">
        import type { Load } from '@sveltejs/kit';
    
        export const load: Load = async ({ page: { params }, fetch }) => {
            // The params object will contain all of the parameters in the route.
            const { slug } = params;
    
            // Now, we'll fetch the blog post from Strapi
            const res = await fetch('http://localhost:1337/posts/' + slug);
    
            // A 404 status means "NOT FOUND"
            if (res.status === 404) {
                // We can create a custom error and return it.
                // SvelteKit will automatically show us an error page that we'll learn to customise later on.
                const error = new Error(`The post with ID ${slug} was not found`);
                return { status: 404, error };
            } else {
                const data = await res.json();
                return { props: { post: data } };
            }
        };
    </script>
    
    <script lang="ts">
        import type { Post } from '$lib/types';
        import { onMount } from 'svelte';
        import { goto } from '$app/navigation';
        import user from '$lib/user';
    
        export let post: Post;
        let content = post.content;
    
        onMount(async () => {
            // Install the marked package first!
            // Run this command: npm i marked
    
            // We're using this style of importing because "marked" uses require, which won't work when we import it with SvelteKit.
            // Check the "How do I use a client-side only library" in the FAQ: https://kit.svelte.dev/faq
            const marked = (await import('marked')).default;
            content = marked(post.content);
        });
    
        async function deletePost() {
            // TODO
        }
    </script>
    
    <h1 class="text-center text-4xl mt-4">{post.title}</h1>
    <p class="text-center mt-2">By: {post.author.username}</p>
    
    {#if $user && post.author.id === $user.id}
        <p class="my-2 flex justify-center items-center gap-3">
            <button
                class="bg-blue-500 text-white font-bold py-2 px-4 rounded border-transparent"
                on:click={() => goto('/new?edit=' + post.id)}>Update post</button
            >
            <button
                class="bg-red-500 text-white font-bold py-2 px-4 rounded border-transparent"
                on:click={deletePost}>Delete post</button
            >
        </p>
    {/if}
    
    <div class="border border-gray-500 my-4 mx-8 p-6 rounded">
        {@html content}
    </div>
```

Now, when the Author visits their PostPost, they'll see two buttons to Update and Delete the PostPost, respectively.

## Deleting posts

Let's add functionality to the `Delete Post` button. Edit the `deletePost()` function in the file we just modified (`src/routes/blog/[slug].svelte`) and change it to this:

```

    if (!localStorage.getItem('token')) {
      goto('/login');
      return;
    }
    
    const res = await fetch('http://localhost:1337/posts/' + post.id, {
      method: 'DELETE',
      headers: { Authorization: 'Bearer ' + localStorage.getItem('token') }
    });
    if (res.ok) {
      goto('/');
    } else {
      const data: { message: { messages: { message: string }[] }[] } = await res.json();
      if (data?.message?.[0]?.messages?.[0]?.message) {
        alert(data.message[0].messages[0].message);
      }
    }
```

Now, obviously, we don't want anybody to delete a post by someone else. Let's add another method in `api/post/controllers/post.js` in our Strapi App.

This is how your code should look now:

```

    // api/post/controllers/post.js
    "use strict";
    
    const { parseMultipartData, sanitizeEntity } = require("strapi-utils");
    
    /**
     * Read the documentation (https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#core-controllers)
     * to customize this controller
     */
    
    module.exports = {
      async create(ctx) {
        let entity;
    
        if (ctx.is("multipart")) {
          const { data, files } = parseMultipartData(ctx);
          data.author = ctx.state.user.id;
          entity = await strapi.services.post.create(data, { files });
        } else {
          ctx.request.body.author = ctx.state.user.id;
          entity = await strapi.services.post.create(ctx.request.body);
        }
    
        return sanitizeEntity(entity, { model: strapi.models.post });
      },
    
      async update(ctx) {
        const { id } = ctx.params;
    
        let entity;
    
        const [article] = await strapi.services.post.find({
          id: ctx.params.id,
          "author.id": ctx.state.user.id,
        });
    
        if (!article) {
          return ctx.unauthorized(`You can't update this entry`);
        }
    
        if (ctx.is("multipart")) {
          const { data, files } = parseMultipartData(ctx);
          entity = await strapi.services.post.update({ id }, data, {
            files,
          });
        } else {
          entity = await strapi.services.post.update({ id }, ctx.request.body);
        }
    
        return sanitizeEntity(entity, { model: strapi.models.post });
      },
    
      async delete(ctx) {
        const { id } = ctx.params;
    
        let entity;
    
        const [article] = await strapi.services.post.find({
          id: ctx.params.id,
          "author.id": ctx.state.user.id,
        });
    
        if (!article) {
          return ctx.unauthorized(`You can't delete this entry`);
        }
    
        await strapi.services.post.delete({ id });
    
        return { ok: true };
      },
    };
```


And now, the author should be able to delete posts.

## Custom error page.

You may have noticed that the 404 page looks terrible. It has almost no styling. With SvelteKit, we're allowed to create a [custom error page](https://kit.svelte.dev/docs#layouts-error-pages). So we need to name this file `__error.svelte` and place it in `src/routes`.

```

    <!-- src/routes/__error.svelte -->
    <script lang="ts" context="module">
        import type { ErrorLoad } from '@sveltejs/kit';
    
        export type { ErrorLoad } from '@sveltejs/kit';
    
        export const load: ErrorLoad = ({ error, status }) => {
            return { props: { error, status } };
        };
    </script>
    
    <script lang="ts">
        export let error: Error;
        export let status: number;
    </script>
    
    <div class="fixed w-full h-full grid place-items-center">
        <section class="p-8 border-gray-500 rounded">
            <h1 class="text-center text-4xl font-mono-mt-4">{status}</h1>
            <p class="text-center">{error.message}</p>
        </section>
    </div>
```

Here's how our error page will look like.


![The new error page](https://i.imgur.com/uNbvTNX.png)


Much better right?

## Conclusion

And there you have it! Your blog website is made with SvelteKit and Strapi. If you got stuck anywhere, be sure to check the [SvelteKit Docs](https://kit.svelte.dev/docs), the [Strapi Docs](https://strapi.io/documentation), and the [source code on Github](https://github.com/arnu515/sveltekit-strapi-blog).