Back to blog
How to Upload Files with SvelteKit and Pinata
You have likely heard of Svelte if you’ve spent time on the web for the last few years, and there’s a good reason for that. Svelte is a JavaScript framework that is compiled, lightweight, and fairly simple to use. It simplifies things that usually require a lot of custom components and code to get working, such as a simple counter.
<script>
let count = 0;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count}
{count === 1 ? 'time' : 'times'}
</button>
Svelte takes what you already know from JavaScript and makes it easy to connect it to your markup, creating a very pleasing developer experience. It’s already adopted by some big names like the New York Times, Squarespace, and even IBM. In this tutorial, we’ll show you how to setup an app using SvelteKit and add file uploads using Pinata! Special shoutout to Jason Gutierrez who made a PR to our docs to add this template to our collection!
Setup
To start, you will need a free Pinata account, which you can create in just a minute or two here. Once you’re in, you’ll want to create an API key, which you can do from the API Keys tab and then clicking New Key in the top right. Give it a name, admin permissions, and be sure to save the JWT
which we’ll be using in just a bit. Then, you’ll want to grab the Gateway domain, which is included with your account from the Gateways tab. It it should look something like aqua-keen-cobra-996.mypinata.cloud
which you can copy down where you kept the JWT
.
Now, we can start up our app, which you can do as long as you have Node.js installed and a text editor. Open your terminal and run this command:
npm create svelte@latest pinata-app
When it gives you the options, we’ll want to just select the skeleton project template. Once it has finished installing the dependencies, run the next command to cd
into the repo and install pinata
.
cd pinata-app && npm i pinata
Finally, we will want to make a new file in the root of the project called env.local
and put in the following values:
PINATA_JWT= # The Pinata API Key JWT we made earlier
PUBLIC_GATEWAY_URL= # The Gateway domain just as we copied it from the Pinata app
Adding Uploads
With our project all setup, we can write some code to upload some files. If you take a look at the project structure, it should look something like this:
.
├── README.md
├── package-lock.json
├── package.json
├── src
│ ├── app.d.ts
│ ├── app.html
│ ├── lib
│ │ └── index.ts
│ └── routes
│ └── +page.svelte
├── static
│ └── favicon.png
├── svelte.config.js
├── tsconfig.json
└── vite.config.ts
Our main focus will be in the lib
folder, as well as the routes
directory, where our pages will be rendered. To start, let’s make a new folder in lib
called server
, and in that new folder make a file called pinata.ts
. This is where our Pinata SDK instance will live, ensuring our API key are not exposed to the client. In pinata.ts
let’s put the following code:
import { PinataSDK } from "pinata"
import { PINATA_JWT } from "$env/static/private";
import { PUBLIC_GATEWAY_URL } from "$env/static/public";
export const pinata = new PinataSDK({
pinataJwt: `${PINATA_JWT}`,
pinataGateway: `${PUBLIC_GATEWAY_URL}`
})
This exports a very simple instance of the SDK using the variables from our .env.local
file. Now, we can setup our client side form in the +page.svelte
file.
<script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
export let form: ActionData;
let uploading = false;
function handleUpload() {
uploading = true;
return async ({ update }) => {
await update();
uploading = false;
};
}
</script>
<main class="w-full min-h-screen m-auto flex flex-col justify-center items-center">
<form method="POST" enctype="multipart/form-data" use:enhance={handleUpload}>
<input type="file" id="file" name="fileToUpload" accept=".jpg, .jpeg, .png, .webp" />
<button disabled={uploading} type="submit">
{uploading ? 'Uploading...' : 'Upload'}
</button>
</form>
</main>
In here, we have a pretty simple form setup using an input and a button to select a file then submit it. We also have a handleUpload
function which will be used by the form, but at the moment it doesn’t do anything. In order to make it work, we need to make a server action by creating a file titled +page.server.ts
in the same directory. Let’s do that now and add this code:
import { fail, json, type Actions } from "@sveltejs/kit";
import { pinata } from "$lib/server/pinata";
export const actions: Actions = {
default: async ({ request }) => {
try {
const formData = await request.formData();
const uploadedFile = (formData?.get('fileToUpload') as File);
if (!uploadedFile.name || uploadedFile.size === 0) {
return fail(400, {
error: true,
message: "You must provide a file to upload"
})
}
const upload = await pinata.upload.file(uploadedFile);
const url = await pinata.gateways.createSignedURL({
cid: upload.cid,
expires: 360
});
return { url, filename: uploadedFile.name, status: 200 };
} catch (error) {
console.log(error);
return json(
{ error: "Internal Server Error" },
{ status: 500 }
);
}
}
}
In this handy little server action, our form will by default send the submission here. Then, we can parse the attached form data, make sure there is a file, and then simply upload it with the Pinata SDK! We’ll go one step further by creating a temporary URL we can send back to the client and view the content. The SDK also makes this a breeze, so all we have to do is return the data back to the client. The only thing we’re missing is that, back in the client, we want to render the returned URL, which we can do by adding an if statement to the +page.svelte
file.
<script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
export let form: ActionData;
let uploading = false;
function handleUpload() {
uploading = true;
return async ({ update }) => {
await update();
uploading = false;
};
}
</script>
<main>
<form method="POST" enctype="multipart/form-data" use:enhance={handleUpload}>
<input type="file" id="file" name="fileToUpload" accept=".jpg, .jpeg, .png, .webp" />
<button disabled={uploading} type="submit">
{uploading ? 'Uploading...' : 'Upload'}
</button>
</form>
{#if form && form.status === 200}
<img src={form.url} alt={form.filename} />
{/if}
</main>
Thats all we need! If you spin up the dev server with npm run dev
you should get something like this:
Wrapping Up
This is only touching the surface of what you could build with SvelteKit and Pinata. Need inspiration? Check out some of our other blog posts and read up on our docs to launch your next app!