DEV Community

Cover image for Building an Open Source DocSend alternative with Next.js, Vercel Blob and Postgres 🚀
Marc Seitz
Marc Seitz

Posted on

Building an Open Source DocSend alternative with Next.js, Vercel Blob and Postgres 🚀

What you will find in this article?

You probably come across platforms for secure document sharing, tracking, and storage like DocSend, Dropbox, Google Drive, and the list goes on. The underlying functionality is you upload a document, share the link with a person or a group, and they can view the document via a unique link.

fun gif

Papermark - the first dynamic open-source alternative to DocSend.

Just a quick background about us. Papermark is the dynamic open-source alternative to DocSend. We basically help to manage secure document sharing, including real-time analytics. All of it is open-source.

I would be super happy if you could give us a star! And let me know in the comments ❤️
https://github.com/mfts/papermark

Papermark Dashboard

Setup the project

Here, I'll guide you through creating the project environment for the document sharing application. We'll set up the Next.js app.

Set up tea

Before you get started, I recommend you to set up a package manager, like tea to handle your development environment.

sh <(curl https://tea.xyz)

# --- OR ---
# using brew
brew install tea
Enter fullscreen mode Exit fullscreen mode

tea let's you focus on developing, it will take care of installing node, npm, vercel and any other packages you may need for development.
Just type the command and the package becomes available - even if you didn't install it before.
The best part, tea installs all packages in a relocatable directory (default: ~/.tea), so you don't have to worry about polluting your system files.

Setting up Next.js with TypeScript and Tailwindcss

We are using create-next-app to generate a new Next.js project. We'll also be using TypeScript and Tailwind CSS, so we'll select those options when prompted.

npx create-next-app

# ---
# you'll be asked the following prompts
What is your project named?  my-app
Would you like to add TypeScript with this project?  Y/N
# select `Y` for typescript
Would you like to use ESLint with this project?  Y/N
# select `Y` for ESLint
Would you like to use Tailwind CSS with this project? Y/N
# select `Y` for Tailwind CSS
Would you like to use the `src/ directory` with this project? Y/N
# select `N` for `src/` directory
What import alias would you like configured? `@/*`
# enter `@/*` for import alias
Enter fullscreen mode Exit fullscreen mode

Setup Vercel Blob

  1. Sign up to vercel.com
  2. Go to https://vercel.com/dashboard/stores
  3. Create a new database; choose "Blob"
  4. Don't forget to add @vercel/blob to your package.json
   npm install @vercel/blob
Enter fullscreen mode Exit fullscreen mode
  1. Done 🎉

Vercel Blob Quickstart

Set up Vercel Postgres

  1. Sign up to vercel.com
  2. Go to https://vercel.com/dashboard/stores
  3. Create a new database; choose "Postgres"
  4. Done 🎉

Vercel Postgres Quickstart

Set up Prisma

Prisma is our database ORM layer of choice. It's a great tool for interacting with Postgres and other databases. We'll use it to create a schema for our database and generate TypeScript types for our database models.

npm install @prisma/client
npm install prisma --save-dev
Enter fullscreen mode Exit fullscreen mode

Building application

  1. Uploading the Document
  2. Generating the Link for the Document
  3. Viewing the Document

#1 Uploading the document to Vercel Blob

First stop is the Document Upload. Here, we will lay the groundwork for our users to upload their documents to our platform. This process involves creating an interface to upload files, utilizing Next.js and Vercel Blob to store these documents, and implementing functionality to handle these files.

// pages/upload.tsx
// this is a simplified example of a file upload form in Next.js
import { useState } from "react";

export default function Form() {
  const [currentFile, setCurrentFile] = useState<File | null>(null);

  const handleSubmit = async (event: any) => {
    event.preventDefault();

    // code to send the file to a serverless API function `/api/upload` 
    // to upload to Vercel Blob 
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Upload a document</h2>

      <label>Upload a document</label>
      <input type="file" onChange={(e) => setCurrentFile(e.target.files?.[0] || null)} />

      <label>Name</label>
      <input type="text" placeholder="Acme Presentation" />

      <button type="submit">Upload document</button>
    </form>
  );
}

Enter fullscreen mode Exit fullscreen mode
// pages/api/upload.ts
import * as vercelBlob from "@vercel/blob";
import { NextResponse, NextRequest } from "next/server";

export const config = {
  runtime: "edge",
};

export default async function upload(request: NextRequest) {
  const form = await request.formData();
  const file = form.get("file") as File;

  const blob = await vercelBlob.put(file.name, file, { access: "public" });

  return NextResponse.json(blob);
}
Enter fullscreen mode Exit fullscreen mode

Upload file

#2 Generating Link to Document

Having tackled the uploading, let's now set our sights on the next milestone: generating a unique link for the uploaded document. This is how we allow users to share their documents with others. Essentially, we need to create a system where every document corresponds to a unique URL on our app.

// pages/api/create-link.ts
import { NextApiRequest, NextApiResponse } from "next";
import prisma from "@/lib/prisma";

export default async function handle(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === "POST") {
    const { name, url } = req.body;

    const document = await prisma.document.create({
      data: {
        name,
        url,
        link: {
          create: {},
        },
      },
      include: {
        link: true,
      }
    });

    return res.status(201).json({ document });
  }
}

Enter fullscreen mode Exit fullscreen mode

When a document is uploaded to Vercel Blob, we'll store the blob URL, a reference to the document, in our Postgres database along with a unique identifier (like a UUID) for the document. When someone navigates to a URL on our platform that includes this identifier, we fetch the corresponding blob URL from Postgres and use it to load the document.

#3 Viewing the document

With the document uploaded and the unique link generated, we're at the last stage of our quest: viewing the document. We need to create a way for users to view the documents they access via the unique link.

We can do this by creating a new Next.js page that matches the URL pattern of our document links. This page will fetch the blob URL from our Postgres database using the document identifier in the URL and load the document from Vercel Blob.

// pages/view/[linkId].tsx
// this is a simple example of a Next.js page that loads a document
import React, {useEffect, useState} from 'react'
import {useRouter} from 'next/router'

const ViewDocumentPage = () => {
    const router = useRouter()
    const { linkId } = router.query
    const [documentURL, setDocumentURL] = useState(null)

    useEffect(() => {
        // code to fetch the document URL from Postgres using linkUUID
        // and then set it as documentURL
    }, [linkUUID])

    if (!documentURL) {
        return <div>Loading...</div>
    }

    return <iframe src={documentURL} width="100%" height="100%" />
}

export default ViewDocumentPage

Enter fullscreen mode Exit fullscreen mode

Conclusion

And just like that, our coding adventure concludes. Together, we've constructed a simple, open-source alternative to DocSend, tapping into the power of Next.js and Vercel Blob. It allows users to upload, share, and view documents, similar to many commercial platforms out there, but with our custom touch.

Thank you for reading. I am Marc, an open-source enthusiast. I am building papermark.io - the dynamic open-source alternative to DocSend.

For me coding is about continuous exploration and learning.
Happy coding, friends!

Help me out!

If you feel like this article helped you understand Vercel Blob better! I would be super happy if you could give us a star! And let me also know in the comments ❤️

https://github.com/mfts/papermark

cat please

Top comments (10)

Collapse
 
matts_r profile image
Matheus H. Ribeiro • Edited

Really great Idea man.
One thing, I suggest try use another service for file storage, like R2 from CloudFlare, It's like two times cheaper or maybe enable an option to save the file in base64 Format (I know is heavy with a lot of files or big files, but you can always use the Index as reference and stream the response to the front end).

Collapse
 
mfts profile image
Marc Seitz

Thanks Matheus for the suggestion! I think there are many great file storage solution. I primarily picked Vercel Blob because it's brand-new. I need a solution for local offline development, so I was thinking minio for that :)

Collapse
 
artola profile image
martin

@mfts Did you manage to setup local offline development for vercel/blob ?

Thread Thread
 
mfts profile image
Marc Seitz

Hey @artola we added s3-compatible API storage options instead of vercel/blob

Collapse
 
talenttinaapi profile image
talent

awesome stuff

Collapse
 
mfts profile image
Marc Seitz

Thanks 🙌

Collapse
 
adophilus profile image
Adophilus

Looks amazing 🤩

Collapse
 
mfts profile image
Marc Seitz

Thank you Adophilus! Let me know if you give it a try

Collapse
 
shnai0 profile image
Iuliia Shnai

Starred! Nice dark mode. I think there is a potential to provide a better tool for startups to share pitchdeck.

I have couple of ideas what to commit:)

Collapse
 
mfts profile image
Marc Seitz

Thanks Iuliia 🙌

I'm also looking for new ideas to add to the project!