npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

next-upload

v0.0.30

Published

A turn-key solution for integrating Next.js with signed & secure file-uploads to an S3 compliant storage service such as R2, AWS, or Minio.

Downloads

382

Readme

🗃️ next-upload

A turn-key solution for integrating Next.js with signed & secure file-uploads to an S3 compliant storage service such as R2, AWS, or Minio. Generates signed URLs for uploading files directly to your storage service and optionally integrates with your database to store additional metadata about your files.

Check out this example of a Next.js codebase showcasing an advanced implementation of next-upload with different storage services and databases.

🚧 Under active development. Expect breaking changes until v1.0.0.

📡 Install

npm install next-upload

yarn add next-upload

pnpm add next-upload

👋 Hello there! Follow me @linesofcode or visit linesofcode.dev for more cool projects like this one.

🚀 Getting Started

First let's create a NextUploadConfig that defines how to connect to a storage service, different types of file uploads, size limits and more. The example below uses AWS S3 as the storage service but you can use any S3 compatible service.

src/app/upload/config.ts

import { type NextUploadConfig } from 'next-upload/client';
import { NextUpload } from 'next-upload';

export const config: NextUploadConfig = {
  maxSize: '1mb',
  bucket: NextUpload.bucketFromEnv('my-project-name'),
  client: {
    region: 'us-west-1',
    endpoint: 'https://s3.us-west-1.amazonaws.com',
    credentials: {
      secretAccessKey: process.env.S3_SECRET_KEY,
      accessKeyId: process.env.S3_ACCESS_KEY,
    },
  },
};

Now to integrate with Next.js we need to create an HTTP route that will handle next-upload related requests such as generating signed URLs. In the example below we are using a POST route at /upload with the Next.js App router. If you are using the Pages router or a different framework you can leverage the NextUpload.pagesApiHandler or NextUpload.rawHandler functions directly to achieve the same result.

🔒 This a good place to add authentication to your upload route to restrict who has access to upload files.

src/app/upload/route.ts

import { NextUpload } from 'next-upload';
import { config } from './config';
import { NextRequest } from 'next/server';

const nup = new NextUpload(config);

export const POST = (request: NextRequest) => nup.handler(request);

export const dynamic = 'force-dynamic';

// Optionally, if your application supports it you can run next-upload in the Edge runtime.
export const runtime = 'edge'; 

At this point you can import helper functions from next-upload/client to send files to your storage service in one line of code.

How your application handles file-uploads in the browser is up to you. The example below uses react-dropzone to send files to storage via the upload function.

src/components/FileUpload.tsx

'use client';

import { useDropzone } from 'react-dropzone';
import { useNextUpload } from 'next-upload/react';

const FileUpload = () => {
  const nup = useNextUpload();

  const onDropAccepted = (acceptedFiles: File[]) =>
    nup.upload(
      acceptedFiles.map((file) => ({
        file,
      }))
    );

  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    onDropAccepted,
  });

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      {isDragActive ? (
        <p>Drop the files here ...</p>
      ) : (
        <p>Drag 'n' drop some files here, or click to select files</p>
      )}
      {nup.isUploading && <p>Uploading...</p>}
    </div>
  );
};

export default FileUpload;

🧳 Storage

It's often useful to save an additional reference to the uploaded file in your database. This can be used for things like displaying a list of files that have been uploaded or associating a file with a specific user within your application without having to query the storage service directly. next-upload provides an interface that can be implemented with any database of your choice.

🙋 Is your database missing? Implementing the Store is very straight-forward. Feel free to open a PR with your implementation or open an issue to request a new asset store implementation.

Out of the box next-upload provides the following asset store implementations:

🗝️ KeyvStore - all popular databases supported

Works with any keyv enabled store. This includes popular databases such as Postgres, MySQL and Mongo. This is a simple option for getting started with an asset store with minimal overhead. Warning: Using keyv is inherently slower than using a more specific database client. If you are expecting a high volume of reads/writes to your asset store you should consider using a different implementation.

src/app/upload/nup.ts

import { nextUploadConfig } from '@/app/upload/config';
import KeyvPostgres from '@keyv/postgres';
import Keyv from 'keyv';
import { NextUpload } from 'next-upload';
import { NextUploadKeyvStore } from 'next-upload/store/keyv';

export const nup = new NextUpload(
  nextUploadConfig,
  new NextUploadKeyvStore(
    new Keyv({
      namespace: NextUpload.namespaceFromEnv('my-project-name'),
      store: new KeyvPostgres({
        uri: `${process.env.PG_CONNECTION_STRING}/${process.env.PG_DB}`,
      }),
    })
  )
);

☔️ Drizzle

Works with a Drizzle enabled database. This is a great option if you are already using Drizzle in your application and want tighter integration with your database schema. It also provides a more performant option for high volume reads/writes to your asset store.

🐘 NextUploadDrizzlePgStore

The following Postgres clients are directly supported. Other Postgres clients most likely will work but may raise TypeScript errors during initialization of the NextUploadDrizzlePgStore instance.

src/db/schema.ts

export { nextUploadAssetsTable } from 'next-upload/store/drizzle/postgres-js';

src/app/upload/nup.ts

import { nextUploadConfig } from '@/app/nextUploadConfig';
import { getDbPostgresJs } from '@/drizzle/getDbPostgresJs';
import { NextUpload } from 'next-upload';
import { NextUploadDrizzlePgStore } from 'next-upload/store/drizzle/postgres-js';

export const nup = new NextUpload(
  nextUploadConfig,
  new NextUploadDrizzlePgStore(getDbPostgresJs())
);

🔗 Getting an Asset Url

Once you have uploaded a file you can retrieve the url, id and metadata of the asset using the NextUpload.getAsset function.

const { asset } = await nup.getAsset({
  id: 'id of the asset',
  // or provide a path
  path: 'path of the asset',
})

🗑️ Deleting Assets

You can delete an asset with NextUpload.deleteAsset. This will delete the asset from your storage service and the asset store if you have one configured.

await nup.deleteAsset({
  id: 'id of the asset',
  // or provide a path
  path: 'path of the asset',
})

🔎 Retrieving Assets

Once you have uploaded a file you can retrieve it from the database using the Store instance.

const store = nup.getStore();

await store.find('id of the asset');

📝 Metadata

Using an Store enables you to save additional metadata about the file as part of the upload process. This can be useful for storing things like the original file name, user id of the uploader, or any other information you want to associate with the file.

To get started simply pass a metadata object to the upload function.

import { upload } from 'next-upload/client';

await upload(
  acceptedFiles.map((file) => ({
    file,
    metadata: {
      userId: '123',
    },
  })),
  config
);

✅ Verifying uploads

In certain scenarios you may need to mark an upload as verified once it has been processed by your application.

To enable verification, set the verifyAssets config to true and instantiate NextUpload with an Store instance.

Now any file that is uploaded will have a verified property set to false by default. Once you have processed the file you can mark it as verified by calling NextUpload.verifyAsset(id).

✂️ Pruning assets

At any time you can call a NextUpload.pruneAssets to delete any assets that have expired due to lack of verification or to remove dangling files or records from the system.

Consider setting up a cron job to run this function on a regular basis.

:wrench: Constants

:gear: defaultEnabledHandlerActions

| Constant | Type | | ---------- | ---------- | | defaultEnabledHandlerActions | NextUploadAction[] |

:factory: NextUpload

Methods

:gear: generatePresignedPostPolicy

| Method | Type | | ---------- | ---------- | | generatePresignedPostPolicy | (args: any, request: NextToolRequest or undefined) => Promise<{ postPolicy: SignedPostPolicy; }> |

:gear: namespaceFromEnv

| Method | Type | | ---------- | ---------- | | namespaceFromEnv | (project?: string or undefined) => string |

:gear: bucketFromEnv

| Method | Type | | ---------- | ---------- | | bucketFromEnv | (project?: string or undefined) => string |

:gear: getIdFromPath

| Method | Type | | ---------- | ---------- | | getIdFromPath | (path: string) => string |

:gear: getUploadTypeFromPath

| Method | Type | | ---------- | ---------- | | getUploadTypeFromPath | (path: string) => string |

:gear: calculateExpires

| Method | Type | | ---------- | ---------- | | calculateExpires | (ttl: number) => number or null |

:gear: isExpired

| Method | Type | | ---------- | ---------- | | isExpired | (timestamp: number or null or undefined) => boolean or 0 |

:gear: getBucket

| Method | Type | | ---------- | ---------- | | getBucket | () => string |

:gear: getClient

| Method | Type | | ---------- | ---------- | | getClient | () => S3 |

:gear: init

| Method | Type | | ---------- | ---------- | | init | () => Promise<void> |

:gear: bucketExists

| Method | Type | | ---------- | ---------- | | bucketExists | () => Promise<boolean> |

:gear: generatePresignedPostPolicy

| Method | Type | | ---------- | ---------- | | generatePresignedPostPolicy | (args: GeneratePresignedPostPolicyArgs, request?: NextUploadRequest or undefined) => Promise<{ postPolicy: SignedPostPolicy; }> |

:gear: pruneAssets

| Method | Type | | ---------- | ---------- | | pruneAssets | () => Promise<boolean> |

:gear: verifyAsset

| Method | Type | | ---------- | ---------- | | verifyAsset | (args: VerifyAssetArgs or VerifyAssetArgs[]) => Promise<{ asset: Asset; assets: Asset[]; }> |

:gear: deleteAsset

| Method | Type | | ---------- | ---------- | | deleteAsset | (args: DeleteArgs or DeleteArgs[]) => Promise<boolean> |

:gear: getAsset

| Method | Type | | ---------- | ---------- | | getAsset | (args: GetAssetArgs or GetAssetArgs[], request?: NextUploadRequest or undefined) => Promise<{ asset: GetAsset; assets: GetAsset[]; }> |

:nut_and_bolt: Enum

:gear: NextUploadAction

| Property | Type | Description | | ---------- | ---------- | ---------- | | deleteAsset | 'deleteAsset' | | | generatePresignedPostPolicy | 'generatePresignedPostPolicy' | | | getAsset | 'getAsset' | | | pruneAssets | 'pruneAssets' | | | verifyAsset | 'verifyAsset' | |