Svelte-strapi-tutorial
Tailwind CSS
Svelte-strapi-tutorial | Tailwind CSS | |
---|---|---|
1 | 1,286 | |
7 | 78,889 | |
- | 1.6% | |
10.0 | 9.4 | |
about 2 years ago | 2 days ago | |
Svelte | TypeScript | |
- | MIT License |
Stars - the number of stars that a project has on GitHub. Growth - month over month growth in stars.
Activity is a relative number indicating how actively a project is being developed. Recent commits have higher weight than older ones.
For example, an activity of 9.0 indicates that a project is amongst the top 10% of the most actively developed projects that we are tracking.
Svelte-strapi-tutorial
-
How to Create a Blog with SvelteKit and Strapi
Title Description Content
Submitlabel { @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; }Enter fullscreen mode Exit fullscreen mode`
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'; /** * post controller */ const { createCoreController } = require('@strapi/strapi').factories; const { parseMultipartData, sanitizeEntity } = require("strapi-utils"); /** * Read the documentation (https://docs.strapi.io/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 }; }, }; module.exports = createCoreController('api::post.post');
Enter fullscreen mode Exit fullscreen mode`
If you get confused, checkout the Strapi Documentation
Install
strapi-utils
with the following command.
bash
npm install strapi-utils
And now, you should be able to create and update posts all from one route. Let's make the update process easier. Changesrc/routes/blog/[id].svelte
to the code below:`js
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ params, fetch }) => {// Now, we'll fetch the blog post from Strapi const res = await fetch(`http://localhost:1337/api/posts/${params.id}?populate=*`); // 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 ${params.id} was not found`); return { status: 404, error }; } else { const response = await res.json(); return { props: { post: response.data.attributes } }; } }; 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 }
{post.title}
By: {post.author.data.attributes.username}
{#if $user && post.author.id === $user.id}goto('/new?edit=' + post.id)}>Update post Delete post
{/if}{@html content}`
Now, when the Author visits their PostPost, they'll see two buttons to Update and Delete the PostPost, respectively.
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 thePost
content type. Here, we'll make it so that the Author of a post will be the currently logged-in User.
Editapi/post/controllers/post.js
in the Strapi project.`
"use strict"; const { parseMultipartData, sanitizeEntity } = require("strapi-utils"); /** * Read the documentation (https://docs.strapi.io/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 the Strapi Documentation
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:`
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 } }; } }; 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 }
{post.title}
By: {post.author.username}
{#if $user && post.author.id === $user.id}goto('/new?edit=' + post.id)}>Update post Delete post
{/if}{@html content}`
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 thedeletePost()
function in the file we just modified (src/routes/blog/[id].svelte
) and change it to this:`
async function deletePost() { if (!localStorage.getItem('token')) { goto('/login'); return; } const res = await fetch('http://localhost:1337/api/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 inapi/post/controllers/post.js
in our Strapi App.This is how your code should look now:
`
'use strict'; /** * post controller */ const { createCoreController } = require('@strapi/strapi').factories; const { parseMultipartData, sanitizeEntity } = require("strapi-utils"); /** * Read the documentation (https://docs.strapi.io/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 }; }, }; module.exports = createCoreController('api::post.post');
`
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. So we need to name this file
__error.svelte
and place it insrc/routes
.`
import type { ErrorLoad } from '@sveltejs/kit'; export type { ErrorLoad } from '@sveltejs/kit'; export const load: ErrorLoad = ({ error, status }) => { return { props: { error, status } }; }; export let error: Error; export let status: number;
{status}
{error.message}
`
Here's how our error page will look like when you search for a blog post with wrong id.
Much better right?
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://docs.strapi.io/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.
Conclusion & Resources
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, the Strapi Docs, and the source code on Github.
Tailwind CSS
-
Essential Tools & Technologies for New Developers
Lastly, Tailwind CSS is a utility-first CSS framework packed with classes like flex, pt-4, text-center, and rotate-90 that can be composed to build any design, directly in your markup.
-
E-commerce checkout components built with Tailwind CSS and Flowbite
Tailwind CSS
-
Hanami and HTMX - progress bar
Sidekiq is already configured along with assets, tailwindsCSS.
- Qu'est-ce qu'un projet MERN Stack et comment créer une application CRUD avec? Partie 2/2, Tutoriel
-
How to Build Your Own ChatGPT Clone Using React & AWS Bedrock
Finally, for our front end, we’re going to be pairing Next.js with the great combination of TailwindCSS and shadcn/ui so we can focus on building the functionality of the app and let them handle making it look awesome!
-
Building an Email Assistant Application with Burr
You can use any frontend framework you want — react-based tooling, however, has a natural advantage as it models everything as a function of state, which can map 1:1 with the concept in Burr. In the demo app we use react, react-query, and tailwind, but we’ll be skipping over this largely (it is not central to the purpose of the post).
-
Shared Data-Layer Setup For Micro Frontend Application with Nx Workspace
Tailwind CSS: A utility-first CSS framework for rapidly building custom designs.
-
Preline UI + Gowebly CLI = ❤️
First, you need to make sure that you have a working Tailwind CSS project…
-
Customer service pages for e-commerce built with Tailwind CSS
Tailwind CSS
-
The best testing strategies for frontends
With better CSS approaches like TailwindCSS and Vanilla Extract (which we're heavily using) it's much easier to maintain the UI and make sure it doesn't change unexpectedly. No more conflicting CSS classes, much less CSS specificity issues and much less CSS code in general.