<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>OpenBucket blog</title>
        <link>https://projectbay.github.io/openbucket/blog</link>
        <description>Tutorials, recipes, and updates from the OpenBucket project.</description>
        <lastBuildDate>Wed, 08 Jul 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Copyright © 2026 OpenBucket contributors.</copyright>
        <item>
            <title><![CDATA[Add S3-compatible file uploads to a NestJS app in 10 minutes]]></title>
            <link>https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes</link>
            <guid>https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes</guid>
            <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Wire up file uploads in a NestJS app with a self-hosted, S3-compatible object store you install as an npm package — no AWS account, no MinIO container.]]></description>
            <content:encoded><![CDATA[<p>Every app eventually needs to store files — avatars, attachments, exports. And in
Node/NestJS the usual options are all a bit of a chore: pay for AWS S3 and juggle
credentials, stand up and babysit a MinIO container, or hack something onto local
disk that you'll have to rip out later.</p>
<p>OpenBucket takes a different route: it's an <strong>S3-compatible object store you
<code>npm install</code> straight into your NestJS app</strong>. No second service, no container in
local dev, no cloud account. In this tutorial we'll add a working upload endpoint —
with validation and a shareable URL — in about ten minutes.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-were-building">What we're building<a href="https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes#what-were-building" class="hash-link" aria-label="Direct link to What we're building" title="Direct link to What we're building" translate="no">​</a></h2>
<p>A <code>POST /files</code> endpoint that accepts a multipart upload, streams it straight into
OpenBucket (no temp file, no <code>file.buffer</code> in memory), validates it, and returns a
short-lived URL to fetch it back. The same object store also gives you a bundled
admin console to browse what you uploaded.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1--install">Step 1 — Install<a href="https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes#step-1--install" class="hash-link" aria-label="Direct link to Step 1 — Install" title="Direct link to Step 1 — Install" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">npm</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> @openbucket/nestjs</span><br></div></code></pre></div></div>
<p><code>multer</code> is an optional peer dependency used for the upload pipeline:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">npm</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> multer</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2--generate-the-secrets">Step 2 — Generate the secrets<a href="https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes#step-2--generate-the-secrets" class="hash-link" aria-label="Direct link to Step 2 — Generate the secrets" title="Direct link to Step 2 — Generate the secrets" translate="no">​</a></h2>
<p>OpenBucket refuses to boot with weak or missing secrets. You need an argon2id hash
of your admin password and a couple of random secrets. The package ships a CLI, so
you can mint the hash with <strong>no repository checkout</strong>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># argon2id hash for the admin password:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx @openbucket/nestjs </span><span class="token builtin class-name">hash</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'choose-a-strong-password'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># two random 32-char secrets (JWT signing + the S3 root secret):</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">openssl rand </span><span class="token parameter variable" style="color:#36acaa">-hex</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">32</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">openssl rand </span><span class="token parameter variable" style="color:#36acaa">-hex</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">32</span><br></div></code></pre></div></div>
<p>Drop them into your environment (e.g. a <code>.env</code>):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token assign-left variable" style="color:#36acaa">OB_ADMIN_HASH</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">'$argon2id$v=19$m=65536,...'</span><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic"># from `openbucket hash`</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token assign-left variable" style="color:#36acaa">OB_JWT_SECRET</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">'…32+ chars…'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token assign-left variable" style="color:#36acaa">OB_ACCESS_KEY_ID</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">'AKIALOCALDEV0000000'</span><span class="token plain">         </span><span class="token comment" style="color:#999988;font-style:italic"># 16–32 uppercase alphanumerics</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token assign-left variable" style="color:#36acaa">OB_SECRET_ACCESS_KEY</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">'…32+ chars…'</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3--register-the-module">Step 3 — Register the module<a href="https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes#step-3--register-the-module" class="hash-link" aria-label="Direct link to Step 3 — Register the module" title="Direct link to Step 3 — Register the module" translate="no">​</a></h2>
<p>Mount OpenBucket under a path prefix in your app. Everything — the S3 endpoint, the
admin API, and the admin console — lives under <code>mountPath</code>.</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">app.module.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> Module </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@nestjs/common'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> OpenBucketModule </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@openbucket/nestjs'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> FilesController </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./files.controller'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">Module</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  imports</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    OpenBucketModule</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">forRoot</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      dataDir</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./.openbucket-data'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// SQLite + blob payloads live here</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      mountPath</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/storage'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// S3 endpoint = http://localhost:3000/storage</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      rootCredentials</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        accessKeyId</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">OB_ACCESS_KEY_ID</span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        secretAccessKey</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">OB_SECRET_ACCESS_KEY</span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      admin</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        username</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'admin'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        passwordHash</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">OB_ADMIN_HASH</span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        jwtSecret</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">OB_JWT_SECRET</span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        serveUi</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// admin console at /storage/admin</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  controllers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">FilesController</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">AppModule</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4--create-a-bucket-on-startup">Step 4 — Create a bucket on startup<a href="https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes#step-4--create-a-bucket-on-startup" class="hash-link" aria-label="Direct link to Step 4 — Create a bucket on startup" title="Direct link to Step 4 — Create a bucket on startup" translate="no">​</a></h2>
<p>Create the <code>uploads</code> bucket once, when the app boots, using the in-process
<code>OpenBucketService</code> (no HTTP round-trip):</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">app.bootstrap.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> Injectable</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> OnModuleInit </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@nestjs/common'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> OpenBucketService </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@openbucket/nestjs'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">Injectable</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">BucketBootstrap</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">implements</span><span class="token plain"> </span><span class="token class-name">OnModuleInit</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">readonly</span><span class="token plain"> ob</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> OpenBucketService</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">onModuleInit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ob</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">bucketExists</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'uploads'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ob</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">createBucket</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'uploads'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Add <code>BucketBootstrap</code> to your module's <code>providers</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5--the-upload-endpoint">Step 5 — The upload endpoint<a href="https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes#step-5--the-upload-endpoint" class="hash-link" aria-label="Direct link to Step 5 — The upload endpoint" title="Direct link to Step 5 — The upload endpoint" translate="no">​</a></h2>
<p>Here's the whole thing. <code>OpenBucketFileInterceptor</code> streams the multipart part
<strong>straight into the store</strong> — no temp file, no <code>file.buffer</code>. The
<code>@UploadedToBucket()</code> decorator hands you the committed object, and
<code>UploadValidationExceptionFilter</code> turns a rejected upload into a clean <code>HTTP 400</code>.</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">files.controller.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Controller</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Post</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  UseFilters</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  UseInterceptors</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@nestjs/common'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  OpenBucketFileInterceptor</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  UploadedToBucket</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  UploadValidationExceptionFilter</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">UploadedFileInfo</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@openbucket/nestjs/multer'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">Controller</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'files'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">FilesController</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">Post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">UseFilters</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">UploadValidationExceptionFilter</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// rejected upload → HTTP 400</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">UseInterceptors</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">OpenBucketFileInterceptor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'file'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      bucket</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'uploads'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      key</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'uuid'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// generate a safe, unique key</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      validate</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        maxBytes</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">10</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 10 MiB</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        allowedContentTypes</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'image/*'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// sniffed, not trusted from the client</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">upload</span><span class="token punctuation" style="color:#393A34">(</span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">UploadedToBucket</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> file</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> UploadedFileInfo</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// `file` is already committed to the store:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// { bucket, key, url?, etag, size, contentType }</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> key</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> file</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">key</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> file</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">url </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>That's the entire feature. Try it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-F</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'file=@./photo.jpg'</span><span class="token plain"> http://localhost:3000/files</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># → { "key": "a1b2c3…", "url": "http://localhost:3000/storage/uploads/a1b2c3…?X-Amz-…" }</span><br></div></code></pre></div></div>
<p>A non-image, or anything over 10 MiB, comes back as a <code>400</code> with a structured
error — no bytes stored.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6--read-it-back">Step 6 — Read it back<a href="https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes#step-6--read-it-back" class="hash-link" aria-label="Direct link to Step 6 — Read it back" title="Direct link to Step 6 — Read it back" translate="no">​</a></h2>
<p><code>file.url</code> is a short-lived presigned GET URL. Store the <strong>stable key</strong> in your
database (not the signed URL), and mint a fresh URL whenever you serve the file:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> OpenBucketService </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@openbucket/nestjs'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// somewhere in a service:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> url </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ob</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">presignGetUrl</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'uploads'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> key</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  baseUrl</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'http://localhost:3000/storage'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  expiresIn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3600</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 1 hour</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-did-the-file-go">Where did the file go?<a href="https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes#where-did-the-file-go" class="hash-link" aria-label="Direct link to Where did the file go?" title="Direct link to Where did the file go?" translate="no">​</a></h2>
<p>Open <strong><a href="http://localhost:3000/storage/admin" target="_blank" rel="noopener noreferrer" class="">http://localhost:3000/storage/admin</a></strong>, sign in with <code>admin</code> and your
password, and you'll see the object in the bucket browser — with an inline preview.
That's the payoff of an object store that lives inside your app: the same engine
runs on your laptop and in production, and you didn't stand up a single extra
service.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-go-next">Where to go next<a href="https://projectbay.github.io/openbucket/blog/nestjs-file-uploads-in-10-minutes#where-to-go-next" class="hash-link" aria-label="Direct link to Where to go next" title="Direct link to Where to go next" translate="no">​</a></h2>
<ul>
<li class=""><strong>Direct browser uploads</strong> without bytes touching your server →
<a class="" href="https://projectbay.github.io/openbucket/docs/guides/sharing-files">presigned POST</a></li>
<li class=""><strong>On-the-fly image resizing</strong> on GET (<code>?w=&amp;h=&amp;format=webp</code>) →
<a class="" href="https://projectbay.github.io/openbucket/docs/guides/image-transforms">image transforms</a></li>
<li class=""><strong>Per-tenant scoped keys</strong> for a multi-tenant SaaS →
<a class="" href="https://projectbay.github.io/openbucket/docs/guides/multi-tenancy">multi-tenancy</a></li>
<li class=""><strong>Durability</strong>: mirror every object to real S3 / R2 / B2 →
<a class="" href="https://projectbay.github.io/openbucket/docs/guides/replication-and-tiering">replication &amp; tiering</a></li>
</ul>
<p>Not sure it fits your use case? Read the honest
<a class="" href="https://projectbay.github.io/openbucket/docs/is-openbucket-for-you">Is OpenBucket for you?</a> guide — it's a single-node
store by design, and we're upfront about when to reach for something else.</p>
<hr>
<p>If this saved you an afternoon, a ⭐ on
<a href="https://github.com/ProjectBay/openbucket" target="_blank" rel="noopener noreferrer" class="">GitHub</a> genuinely helps others find the
project. Questions? Come say hi in
<a href="https://github.com/ProjectBay/openbucket/discussions" target="_blank" rel="noopener noreferrer" class="">Discussions</a>.</p>]]></content:encoded>
            <category>nestjs</category>
            <category>tutorial</category>
            <category>file-upload</category>
            <category>s3</category>
            <category>self-hosted</category>
        </item>
    </channel>
</rss>