cloudflare-r2-uploader 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +214 -0
  2. package/package.json +25 -0
  3. package/src/index.js +74 -0
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # Cloudflare R2 Uploader
2
+
3
+ A lightweight and simple uploader for **Cloudflare R2** that works with **Next.js**, **Node.js**, and any environment that supports the Web `File` API.
4
+
5
+ Upload a `File` object to your Cloudflare R2 bucket and instantly get its public URL.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - 🚀 Simple API
12
+ - 📁 Upload to custom folders
13
+ - 🖼️ File type validation
14
+ - 📏 File size validation
15
+ - 🔑 Automatic UUID file names
16
+ - 🌐 Returns the public file URL
17
+ - ⚡ Built on the official AWS S3 SDK
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install cloudflare-r2-uploader
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Environment Variables
30
+
31
+ Create a `.env` file and add the following variables:
32
+
33
+ ```env
34
+ R2_BUCKET=idhomesolutions
35
+ R2_PUBLIC_URL=https://pub-xxxxxxxxxxxxxxxx.r2.dev
36
+ R2_ACCOUNT_ID=xxxxxxxxxxxxxxxx
37
+ R2_ACCESS_ID=xxxxxxxxxxxxxxxx
38
+ R2_SECRET_KEY=xxxxxxxxxxxxxxxx
39
+ ```
40
+
41
+ | Variable | Description |
42
+ |----------|-------------|
43
+ | `R2_BUCKET` | Your Cloudflare R2 bucket name |
44
+ | `R2_PUBLIC_URL` | Your bucket's public URL |
45
+ | `R2_ACCOUNT_ID` | Cloudflare Account ID |
46
+ | `R2_ACCESS_ID` | R2 Access Key ID |
47
+ | `R2_SECRET_KEY` | R2 Secret Access Key |
48
+
49
+ ---
50
+
51
+ ## Basic Usage
52
+
53
+ ```js
54
+ import upload from "cloudflare-r2-uploader"
55
+
56
+ const url = await upload(file)
57
+
58
+ console.log(url)
59
+ ```
60
+
61
+ ### Returns
62
+
63
+ ```text
64
+ https://pub-xxxxxxxxxxxxxxxx.r2.dev/f87dbb66-4e88-4c47-8fc3-6b74dd4d2b9a.jpg
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Upload to a Folder
70
+
71
+ ```js
72
+ const url = await upload(file, {
73
+ folder: "avatars"
74
+ })
75
+ ```
76
+
77
+ ### Returns
78
+
79
+ ```text
80
+ https://pub-xxxxxxxxxxxxxxxx.r2.dev/avatars/f87dbb66-4e88-4c47-8fc3-6b74dd4d2b9a.jpg
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Custom Validation
86
+
87
+ ```js
88
+ await upload(file, {
89
+ folder: "products",
90
+ maxSize: 10 * 1024 * 1024,
91
+ allowedTypes: [
92
+ "image/",
93
+ "application/pdf"
94
+ ]
95
+ })
96
+ ```
97
+
98
+ ---
99
+
100
+ ## API
101
+
102
+ ### upload(file, options)
103
+
104
+ ### Parameters
105
+
106
+ | Name | Type | Required | Description |
107
+ |------|------|----------|-------------|
108
+ | `file` | `File` | ✅ | File to upload |
109
+ | `options.folder` | `string` | No | Upload inside a folder |
110
+ | `options.maxSize` | `number` | No | Maximum file size in bytes |
111
+ | `options.allowedTypes` | `string[]` | No | Allowed MIME types |
112
+
113
+ ### Default Options
114
+
115
+ ```js
116
+ {
117
+ folder: "",
118
+ maxSize: 5 * 1024 * 1024,
119
+ allowedTypes: [
120
+ "image/"
121
+ ]
122
+ }
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Allow Multiple File Types
128
+
129
+ ```js
130
+ await upload(file, {
131
+ allowedTypes: [
132
+ "image/",
133
+ "application/pdf",
134
+ "video/",
135
+ "audio/"
136
+ ]
137
+ })
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Allow Only PDFs
143
+
144
+ ```js
145
+ await upload(file, {
146
+ allowedTypes: [
147
+ "application/pdf"
148
+ ]
149
+ })
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Increase Maximum Upload Size
155
+
156
+ ```js
157
+ await upload(file, {
158
+ maxSize: 20 * 1024 * 1024
159
+ })
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Error Handling
165
+
166
+ ```js
167
+ try {
168
+ const url = await upload(file)
169
+ } catch (error) {
170
+ console.error(error.message)
171
+ }
172
+ ```
173
+
174
+ Possible error messages:
175
+
176
+ | Error | Reason |
177
+ |-------|--------|
178
+ | `File is required` | No file was provided |
179
+ | `Invalid file` | Value is not a `File` object |
180
+ | `File size must be under X MB` | File exceeds the maximum allowed size |
181
+ | `Only image files are allowed` | Uploaded file type is not allowed |
182
+
183
+ ---
184
+
185
+ ## Example
186
+
187
+ ```js
188
+ import upload from "cloudflare-r2-uploader"
189
+
190
+ const imageUrl = await upload(file, {
191
+ folder: "users",
192
+ maxSize: 8 * 1024 * 1024,
193
+ allowedTypes: [
194
+ "image/"
195
+ ]
196
+ })
197
+
198
+ console.log(imageUrl)
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Requirements
204
+
205
+ - Node.js 18+
206
+ - Cloudflare R2 Bucket
207
+ - Public Bucket URL
208
+ - Valid R2 API Keys
209
+
210
+ ---
211
+
212
+ ## License
213
+
214
+ MIT
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "cloudflare-r2-uploader",
3
+ "version": "1.0.0",
4
+ "description": "Simple Cloudflare R2 uploader",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src"
12
+ ],
13
+ "keywords": [
14
+ "cloudflare",
15
+ "r2",
16
+ "upload",
17
+ "storage",
18
+ "nextjs"
19
+ ],
20
+ "author": "Muhammad Jazib",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "@aws-sdk/client-s3": "^3.1090.0"
24
+ }
25
+ }
package/src/index.js ADDED
@@ -0,0 +1,74 @@
1
+ import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"
2
+ import crypto from "crypto"
3
+
4
+ const required = [
5
+ "R2_BUCKET",
6
+ "R2_PUBLIC_URL",
7
+ "R2_ACCOUNT_ID",
8
+ "R2_ACCESS_ID",
9
+ "R2_SECRET_KEY"
10
+ ]
11
+
12
+ for (const key of required) {
13
+ if (!process.env[key])
14
+ throw new Error(`Missing environment variable: ${key}`)
15
+ }
16
+
17
+ const client = new S3Client({
18
+ region: "auto",
19
+ endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
20
+ credentials: {
21
+ accessKeyId: process.env.R2_ACCESS_ID,
22
+ secretAccessKey: process.env.R2_SECRET_KEY
23
+ }
24
+ })
25
+
26
+ export default async function upload(
27
+ file,
28
+ {
29
+ folder = "",
30
+ maxSize = 5 * 1024 * 1024,
31
+ allowedTypes = ["image/"]
32
+ } = {}
33
+ ) {
34
+ if (!file)
35
+ throw new Error("File is required")
36
+
37
+ if (!(file instanceof File))
38
+ throw new Error("Invalid file. Expected a File object.")
39
+
40
+ if (file.size > maxSize)
41
+ throw new Error(`File size must be under ${maxSize / 1024 / 1024} MB`)
42
+
43
+ const isAllowed = allowedTypes.some(type => file.type.startsWith(type))
44
+
45
+ if (!isAllowed)
46
+ throw new Error(
47
+ `File type "${file.type}" is not allowed.`
48
+ )
49
+
50
+ const extension = file.name.includes(".")
51
+ ? file.name.split(".").pop()
52
+ : ""
53
+
54
+ const fileName = extension
55
+ ? `${crypto.randomUUID()}.${extension}`
56
+ : crypto.randomUUID()
57
+
58
+ const key = folder
59
+ ? `${folder.replace(/^\/|\/$/g, "")}/${fileName}`
60
+ : fileName
61
+
62
+ const buffer = Buffer.from(await file.arrayBuffer())
63
+
64
+ await client.send(
65
+ new PutObjectCommand({
66
+ Bucket: process.env.R2_BUCKET,
67
+ Key: key,
68
+ Body: buffer,
69
+ ContentType: file.type
70
+ })
71
+ )
72
+
73
+ return `${process.env.R2_PUBLIC_URL.replace(/\/$/, "")}/${key}`
74
+ }