Back to blog
How to Upload Files with Deno and Pinata
Node.js completely changed the JavaScript ecosystem, but not without its own issues. Even the creator of Node.js, Ryan Dahl, admitted later on that he had his own regrets over the design of Node. That’s what eventually spurred him to start Deno (if those letters look familiar, 'node'.split('').sort().join('')
), a new JavaScript runtime with security for the modern web. It’s been on the outskirts of the JavaScript ecosystem for years, but its recent v2.0 release might bring it out of the shadows and become a competitor. Some of its main features include zero config typescript, a tight security protocol, and the ability to use native Deno packages or npm packages.
In this guide, we’ll show you some of the basics and how you can integrate Pinata into a Deno stack - taking advantage of all those cool new abilities!
Setup
Before we start writing any code, we’ll need to setup just a few things.
Pinata
Of course, you’ll want to sign up for a free Pinata account. Once you’re inside the Pinata app, head over to the API Keys tab on the left, then create a new API key. Give it admin permissions with unlimited uses, and be sure to save the three keys somewhere safe (we’ll just be using the longer JWT
). Next, go to the Gateways tab on the left sidebar, and copy your provided gateway domain in the format some-example.mypinata.cloud
. That’s all you need!
Project
This project will be pretty simple, so you’ll only need a few things for your dev setup, including the following:
- Deno (install here)
- A text editor like VSCode (I personally like Zed)
- A fun image, like this one of Pinnie
That’s it! Told ya it wasn’t much. Now, in your terminal, go ahead and run the following commands one at a time:
mkdir pinata-deno
cd pinata-deno
deno init
echo .env > .gitignore
touch local.ts server.ts pinata.ts .env
This is going to do the following:
- Make a new project directory
- Move into that directory
- Initialize it with Deno
- Make a
.gitignore
file to ignore our .env file - Create our
local.ts
,server.ts
,pinata.ts
, and.env
files
Open the folder in your text editor and open the .env
file first, then paste in the following variables:
PINATA_JWT= # Your Pinata JWT API key you made earlier
GATEWAY_URL= # The Gateway domain that came with your account in the form example.mypinata.cloud
Put in your own variables for those two spots, and you should be good to go. Next, open the pinata.ts
file and put in the code below:
import { PinataSDK } from "npm:pinata";
import "jsr:@std/dotenv/load";
export const pinata = new PinataSDK({
pinataJwt: Deno.env.get("PINATA_JWT"),
pinataGateway: Deno.env.get("GATEWAY_URL"),
});
This is going to do a few things which are pretty unique to Deno. The first is the package import from NPM. Normally, we would install the Pinata SDK by running npm i pinata
in the terminal, but by importing it with the syntax import { PinataSDK } from "npm:pinata"
, Deno will automatically take care of that for us. It’s also going to import dotenv
to make sure our environment variables load. Lastly we just export an instance of the SDK so we can use it in our other files. Everything is setup, so let’s start uploading some files!
Local Files
We’ll be following a similar pattern to our Bun tutorial, but we’ll see several differences. To start out, we’ll upload a local file to Pinata, in particular the pinnie.png
file we downloaded in our setup. Open the local.ts
file from earlier and put in the following code:
import { pinata } from "./pinata.ts";
(async () => {
const buffer = Deno.readFileSync("pinnie.png");
const file = new File([buffer], "pinnie.png", { type: "image/png" });
const upload = await pinata.upload.file(file);
console.log(upload);
})();
This little file is going to import our pinata
SDK instance, then call an immediately invoked function. Inside that function, we’ll create a buffer of our pinnie.png
file by using Deno.readFileSync
, which is part of Deno’s standard library. Then, we can pass that buffer into a File
object, and simply upload that file to Pinata! To try it out, run deno local.ts
. Once you do this, you might see a lot of confirmation screens like this one:
┏ ⚠️ Deno requests read access to "/Users/user/Desktop/pinata-deno-demo/.env".
┠─ Requested by `Deno.readFileSync()` API.
┠─ Learn more at: <https://docs.deno.com/go/--allow-read>
┠─ Run again with --allow-read to bypass this prompt.
This part of Deno’s security features, which helps make sure that you are granting permissions for specific actions as well as packages. There has been several incidents in the last few months where malicious code is found in popular open source packages, and features like these could help developers keep their machine and dev environments more secure. In our case, you can either keep tapping y
to approve, or you can run the command like this:
deno run --allow-net --allow-read --allow-env local.ts
If all goes well, then you should get an upload response!
{
id: "63ac014d-1be0-4967-8883-55f2316efedd",
name: "pinnie.png",
cid: "bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4",
created_at: "2024-09-09T03:37:44.133Z",
size: 32928,
number_of_files: 1,
mime_type: "image/png",
user_id: "998e0e27-95f1-4237-a3a6-d6a842ba7d60",
is_duplicate: false
}
Server
In our next use case, we’ll make a small little web server using Deno’s native serve
method. Open the server.ts
file and put in the code below:
import { pinata } from "./pinata.ts";
async function handler(req: Request): Promise<Response> {
const path = new URL(req.url).pathname;
if (path === "/") return new Response("Welcome to Deno!");
if (req.method === "POST" && path === "/upload") {
const data = await req.formData();
const file = data.get("file") as File;
const upload = await pinata.upload.file(file);
return Response.json(upload);
}
return new Response("Path not found", { status: 404 });
}
Deno.serve(handler);
This is going to import our SDK instance once again, and then declare a handler
function which takes in a request param and returns a Promise
response. First, we determine the path, then make sever if statements based on the route the user sends a request to. If it’s just a GET
request to the root /
, then we’ll send a simple “Welcome to Deno!” message back. If the path is /upload
and it’s a POST
request, then we’ll do a bit more. First, we’ll parse the incoming formData
and grab the attached file
. If there isn’t a file attached, then we’ll send an error message. Otherwise, we’ll upload the file and return the response from Pinata. To test it out, run this command in your terminal:
deno run --allow-net --allow-read --allow-env --watch server.ts
While that’s running, open another terminal window and run this command in same project root directory:
curl -X POST "<http://localhost:8000/upload>" \\
-F [email protected]
If it works, then you should get a response similar to our previous one with the local file!
Wrapping Up
Only time will tell if Deno will emerge as a new standard for running JavaScript, but in the meantime, I think it’s a unique tool to experiment with. In either case, Pinata is ready to help support you upload and retrieve files securely, no matter what you’re running.
Be sure to sign up today and see why thousands of developers love using Pinata!