Building an Open Source DocSend alternative with Next.js, Vercel Blob and Postgres 🚀
Marc Seitz
Posted on June 7, 2023
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.
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
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
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
Setup Vercel Blob
- Sign up to vercel.com
- Go to https://vercel.com/dashboard/stores
- Create a new database; choose "Blob"
- Don't forget to add @vercel/blob to your package.json
npm install @vercel/blob
- Done 🎉
Set up Vercel Postgres
- Sign up to vercel.com
- Go to https://vercel.com/dashboard/stores
- Create a new database; choose "Postgres"
- Done 🎉
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
Building application
- Uploading the Document
- Generating the Link for the Document
- 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>
);
}
// 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);
}
#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 });
}
}
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
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 ❤️
Posted on June 7, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.