1"use strict";(self.webpackChunkankr_docs_v2=self.webpackChunkankr_docs_v2||[]).push([["16943"],{38899(e,n,t){t.r(n),t.d(n,{metadata:()=>s,default:()=>p,frontMatter:()=>o,contentTitle:()=>a,toc:()=>c,assets:()=>l});var s=JSON.parse('{"id":"basic-tutorials/backend-nodejs-ipfs-file-uploader-with-filebase","title":"Create A Backend NodeJS IPFS File Uploader With Filebase","description":"By Manny If you\u2019d like to understand IPFS a bit better, check out the last article written to easily understand & upload files to IPFS with filebase. This t.","source":"@site/docs/basic-tutorials/backend-nodejs-ipfs-file-uploader-with-filebase.mdx","sourceDirName":"basic-tutorials","slug":"/basic-tutorials/backend-nodejs-ipfs-file-uploader-with-filebase","permalink":"/docs/basic-tutorials/backend-nodejs-ipfs-file-uploader-with-filebase","draft":false,"unlisted":false,"editUrl":"https://github.com/w3tech/ankr-docs/tree/main/docs/basic-tutorials/backend-nodejs-ipfs-file-uploader-with-filebase.mdx","tags":[],"version":"current","lastUpdatedAt":1790023853000,"sidebarPosition":5,"frontMatter":{"title":"Create A Backend NodeJS IPFS File Uploader With Filebase","description":"By Manny If you\u2019d like to understand IPFS a bit better, check out the last article written to easily understand & upload files to IPFS with filebase. This t.","sidebar_position":5,"schema_type":"tutorial"},"sidebar":"tutorialSidebar","previous":{"title":"Filebase to store files to IPFS","permalink":"/docs/basic-tutorials/file-storage-on-ipfs-with-filebase"},"next":{"title":"Deploy and Mint a CryptoKitties-Like NFT with ERC-721 Smart Contract","permalink":"/docs/smart-contract-tutorials/erc-721-smart-contract-on-avalanche"}}'),r=t(74848),i=t(28453);let o={title:"Create A Backend NodeJS IPFS File Uploader With Filebase",description:"By Manny If you\u2019d like to understand IPFS a bit better, check out the last article written to easily understand & upload files to IPFS with filebase. This t.",sidebar_position:5,schema_type:"tutorial"},a="Create A Backend NodeJS IPFS File Uploader With Filebase",l={},c=[{value:"Requirements",id:"requirements",level:2},{value:"Node Project Setup",id:"node-project-setup",level:2},{value:"Creating Initial Server Endpoints",id:"creating-initial-server-endpoints",level:2},{value:"Adding Filebase IPFS Support",id:"adding-filebase-ipfs-support",level:2},{value:"What\u2019s Next?",id:"whats-next",level:3}];function d(e){let n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",hr:"hr",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"create-a-backend-nodejs-ipfs-file-uploader-with-filebase",children:"Create A Backend NodeJS IPFS File Uploader With Filebase"})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"By"})," ",(0,r.jsx)(n.a,{href:"https://twitter.com/codingwithmanny",children:(0,r.jsx)(n.strong,{children:"Manny"})})]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h1,{id:"ipfs--filebase",children:"IPFS & Filebase"}),"\n",(0,r.jsx)(n.p,{children:"If you\u2019d like to understand IPFS a bit better, check out the last article written to easily understand & upload files to IPFS with filebase. This tutorial will focus on creating a Node Backend API that allows for uploading to IPFS with Filebase."}),"\n",(0,r.jsx)("img",{src:"/docs/img/tutorials/ww1.png",alt:"ipfs",className:"responsive-pic",width:"700"}),"\n",(0,r.jsx)(n.h2,{id:"requirements",children:"Requirements"}),"\n",(0,r.jsx)(n.p,{children:"Before we start, we need to make sure that you have the following installed locally on your computer."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://github.com/nvm-sh/nvm",children:"NVM"})," or ",(0,r.jsx)(n.a,{href:"https://nodejs.org/en/",children:"Node v16.15.1"})]}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://yarnpkg.com/",children:"Yarn"})}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://www.postman.com/",children:"Postman"})," (Optional - will also show with curl)"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"node-project-setup",children:"Node Project Setup"}),"\n",(0,r.jsx)(n.p,{children:"The first step is to setup our project from scratch with Express & Typescript."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'mkdir node-filebase-ipfs-uploader;\ncd node-filebase-ipfs-uploader;\nmkdir bucket; # Folder we\'ll be using for local testing\ntouch bucket/.gitkeep;
1 # To make sure we keep the folder and not its file\nmkdir src; # Where our code will live\necho "16.15.1" > .nvmrc;\nnvm install; # ignore if you already have Node 16.15.1 installed\nyarn init -y;\ngit init;\necho "node_modules\\n.env\\nbucket/*\\n\\!bucket/.gitkeep\\nbuild\\n*.log" > .gitignore;\nyarn add aws-sdk cors express dotenv multer multer-s3 typescript @aws-sdk/client-s3 @types/cors @types/express @types/multer-s3 @types/node;\nyarn add -D nodemon ts-node;\n./node_modules/.bin/tsc --init; # generates our tsconfig.json file\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Now that we have our project setup, let\u2019s make a modification to our TypeScript config file to adjust for the output folder to be set to ",(0,r.jsx)(n.code,{children:"./build"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["File: ",(0,r.jsx)(n.code,{children:"./tsconfig.json"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n "compilerOptions": {\n /* Visit https://aka.ms/tsconfig to read more about this file */\n\n /* Projects */\n // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */\n // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */\n // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */\n // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */\n // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */\n // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */\n\n /* Language and Environment */\n "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,\n // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */\n // "jsx": "preserve", /* Specify what JSX code is generated. */\n // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */\n // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */\n // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. \'React.createElement\' or \'h\'. */\n // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. \'React.Fragment\' or \'Fragment\'. */\n // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using \'jsx: react-jsx*\'. */\n // "reactNamespace": "", /* Specify the object invoked for \'createElement\'. This only applies when targeting \'react\' JSX emit. */\n // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */\n // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */\n // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */\n\n /* Modules */\n "module": "commonjs" /* Specify what module code is generated. */,\n // "rootDir": "./", /* Specify the root folder within your source files. */\n "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,\n // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */\n // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */\n // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */\n // "typeRoots": [], /* Specify multiple folders that act like \'./node_modules/@types\'. */\n // "types": [], /* Specify type package names to be included without being referenced in a source file. */\n // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */\n // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */\n // "resolveJsonModule": true, /* Enable importing .json files. */\n // "noResolve": true, /* Disallow \'import\'s, \'require\'s or \'<reference>
1\'s from expanding the number of files TypeScript should add to a project. */\n\n /* JavaScript Support */\n // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the \'checkJS\' option to get errors from these files. */\n // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */\n // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from \'node_modules\'. Only applicable with \'allowJs\'. */\n\n /* Emit */\n // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */\n // "declarationMap": true, /* Create sourcemaps for d.ts files. */\n // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */\n "sourceMap": true /* Create source map files for emitted JavaScript files. */,\n // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If \'declaration\' is true, also designates a file that bundles all .d.ts output. */\n "outDir": "./build" /* Specify an output folder for all emitted files. */,\n // "removeComments": true, /* Disable emitting comments. */\n // "noEmit": true, /* Disable emitting files from a compilation. */\n // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */\n // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */\n // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */\n // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */\n // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */\n // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */\n // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */\n // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */\n // "newLine": "crlf", /* Set the newline character for emitting files. */\n // "stripInternal": true, /* Disable emitting declarations that have \'@internal\' in their JSDoc comments. */\n // "noEmitHelpers": true, /* Disable generating custom helper functions like \'__extends\' in compiled output. */\n // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */\n // "preserveConstEnums": true, /* Disable erasing \'const enum\' declarations in generated code. */\n // "declarationDir": "./", /* Specify the output directory for generated declaration files. */\n // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */\n\n /* Interop Constraints */\n // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */\n // "allowSyntheticDefaultImports": true, /* Allow \'import x from y\' when a module doesn\'t have a default export. */\n "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables \'allowSyntheticDefaultImports\' for type compatibility. */,\n // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */\n "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,\n\n /* Type Checking */\n "strict": true /* Enable all strict type-checking options. */,\n // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied \'any\' type. */\n // "strictNullChecks": true, /* When type checking, take into account \'null\' and \'undefined\'. */\n // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */\n // "strictBindCallApply": true, /* Check that the arguments for \'bind\', \'call\', and \'apply\' methods match the original function. */\n // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */\n // "noImplicitThis": true, /* Enable error reporting when \'this\' is given the type \'any\'. */\n // "useUnknownInCatchVariables": true, /* Default catch clause variables as \'unknown\' instead of \'any\'. */\n // "alwaysStrict": true, /* Ensure \'use strict\' is always emitted. */\n // "noUnusedLocals": true, /* Enable error reporting when local variables aren\'t read. */\n // "noUnusedParameters": true, /* Raise an error when a function parameter isn\'t read. */\n // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding \'undefined\'. */\n // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */\n // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */\n // "noUncheckedIndexedAccess": true, /* Add \'undefined\' to a type when accessed using an index. */\n // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */\n // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */\n // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */\n // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */\n\n /* Completeness */\n // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */\n "skipLibCheck": true /* Skip type checking all .d.ts files. */\n }\n}\n'})}),"\n",(0,r.jsx)(n.h2,{id:"creating-initial-server-endpoints",children:"Creating Initial Server Endpoints"}),"\n",(0,r.jsx)(n.p,{children:"Now that we have our initial project configuration and files setup, we\u2019re going to create two new files that host both the endpoints and the server respectively."}),"\n",(0,r.jsx)(n.p,{children:"The first file is the endpoints and our initial Express server configurations with a single get endpoint on the root to verify that the server is working."}),"\n",(0,r.jsxs)(n.p,{children:["File: ",(0,r.jsx)(n.code,{children:"./src/app.ts"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-typescript",children:'// Imports\n// ========================================================\nimport { config } from "dotenv";\nimport express from "express";\nimport cors from "cors";\n\n// ENV VARS\n// ========================================================\nconfig();\n\nconst NODE_ENV: string = process.env.NODE_ENV || "development";
1\n\n// Init\n// ========================================================\n/**\n * Initial ExpressJS\n */\nconst app = express();\n\n// Middlewares\n// ========================================================\n/**\n * Allows for requests from other servers\n */\napp.use(cors());\n\n// Endpoints / Routes\n// ========================================================\n/**\n * Main endpoint to verify that things are working and what environment mode it\'s running in\n */\napp.get("/", (_req, res) => res.send({ environment: NODE_ENV }));\n\n// Exports\n// ========================================================\nexport default app;\n'})}),"\n",(0,r.jsx)(n.p,{children:"Our second file imports the endpoints and runs them on a specific server. Typically you might have seen both of these in the same files, but for testing (covered in another article), it\u2019s typically easier to run unit and integration tests without the server running to separate concerns."}),"\n",(0,r.jsxs)(n.p,{children:["File: ",(0,r.jsx)(n.code,{children:"./src/server.ts"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-typescript",children:'// Imports\n// ========================================================\nimport app from "./app";\nimport { config } from "dotenv";\n\n// ENV VARS\n// ========================================================\nconfig();\nconst NODE_ENV: string = process.env.NODE_ENV || "development";\nconst PORT: number =\n NODE_ENV === "production" ? 8080 : parseInt(process.env.PORT || "5001", 10);\n\n// Server\n// ========================================================\napp.listen(PORT, () =>\n console.log(`Listening on PORT ${PORT}\\nEnvironment: ${NODE_ENV}`)\n);\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Next, let\u2019s make it easier for ourselves by adding a script command to our ",(0,r.jsx)(n.code,{children:"package.json"})," file, so we can run ",(0,r.jsx)(n.code,{children:"yarn dev"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["File: ",(0,r.jsx)(n.code,{children:"./package.json"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'{\n "name": "node-filebase-ipfs-uploader",\n "version": "1.0.0",\n "main": "index.js",\n "license": "MIT",\n "scripts": {\n "dev": "nodemon src/server.ts"\n },\n "dependencies": {\n "@aws-sdk/client-s3": "^3.113.0",\n "@types/cors": "^2.8.12",\n "@types/express": "^4.17.13",\n "@types/multer-s3": "^3.0.0",\n "@types/node": "^18.0.0",\n "aws-sdk": "^2.1157.0",\n "cors": "^2.8.5",\n "dotenv": "^16.0.1",\n "express": "^4.18.1",\n "multer": "^1.4.5-lts.1",\n "multer-s3": "^3.0.1",\n "typescript": "^4.7.4"\n },\n "devDependencies": {\n "nodemon": "^2.0.16",\n "ts-node": "^10.8.1"\n }\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Now if we run ",(0,r.jsx)(n.code,{children:"yarn dev"})," and open up our browser to ",(0,r.jsx)(n.code,{children:"http://localhost:5001"})," we should see our server running."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"# /node-filebase-ipfs-uploader\nyarn dev;\n\n# Expected Output\n# $ nodemon src/server.ts\n# [nodemon] 2.0.16\n# [nodemon] to restart at any time, enter `rs`\n# [nodemon] watching path(s): *.*\n# [nodemon] watching extensions: ts,json\n# [nodemon] starting `ts-node src/server.ts`\n# Listening on PORT 5001\n# Environment: development\n"})}),"\n",(0,r.jsx)("img",{src:"/docs/img/tutorials/ww2.png",alt:"ipfs",className:"responsive-pic",width:"700"}),"\n",(0,r.jsx)(n.p,{children:"Our server is now working, now we need to add upload functionality."}),"\n",(0,r.jsxs)(n.p,{children:["Adding Local File Upload\nIn order to get a file uploader code setup, we\u2019re going to take advantage of an npm package called multer. ",(0,r.jsx)(n.a,{href:"https://www.npmjs.com/package/multer",children:"Multer"})," is a middleware that makes developer\u2019s lives easier when handling files. It is originally done for just local development, but there are additional extensions that support AWS S3, which we\u2019ll be using for Filebase."]}),"\n",(0,r.jsx)(n.p,{children:"We\u2019re going to modify our original endpoints file to include a new POST upload endpoint that utilizes multer."}),"\n",(0,r.jsxs)(n.p,{children:["File: ",(0,r.jsx)(n.code,{children:"./src/app.ts"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-typescript",children:'// Imports\n// ========================================================\nimport { config } from "dotenv";\nimport express from "express";\nimport cors from "cors";\nimport multer from "multer";\n\n// ENV VARS\n// ========================================================\nconfig();\nconst NODE_ENV: string = process.env.NODE_ENV || "development";\nconst FILE_DEST: string = process.env.FILE_DEST || "bucket";\nconst FILE_SERVER_URL: string =\n process.env.FILE_SERVER_URL || "http://localhost:5002";
1\n\n// Init\n// ========================================================\n/**\n * Initial ExpressJS\n */\nconst app = express();\n\n// Middlewares\n// ========================================================\n/**\n * Allows for requests from other servers\n */\napp.use(cors());\n\n/**\n * Main uploader middleware that configures the final `destination` of the file and how the `filename` would be set once saved\n */\nconst upload = multer({\n storage: multer.diskStorage({\n destination: (_req, file, callback) => {\n callback(null, FILE_DEST);\n },\n filename: (_req, file, callback) => {\n callback(null, file.originalname);\n },\n }),\n});\n\n// Endpoints / Routes\n// ========================================================\n/**\n * Main endpoint to verify that things are working and what environment mode it\'s running in\n */\napp.get("/", (_req, res) => res.send({ environment: NODE_ENV }));\n\n/**\n * Upload endpoint that accepts an input file field of `file`\n */\napp.post("/upload", upload.single("file"), (req, res) => {\n const responseData = {\n file: req.file?.originalname,\n url: `${FILE_SERVER_URL}/${req.file?.originalname}`,\n };\n\n return res.json({ data: responseData });\n});\n\n// Exports\n// ========================================================\nexport default app;\n'})}),"\n",(0,r.jsx)(n.p,{children:"While the server is running, let\u2019s make an upload."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Curl:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'# /node-filebase-ipfs-uploader\n\ncurl --location --request POST \'http://localhost:5001/upload\' \\\n--form \'file=@"/full/path/to/node-filebase-ipfs-uploader/test/test-forever.jpg"\';\n\n# Expected Output\n# {"data":{"file":"test-forever.jpg","url":"http://localhost:5002/test-forever.jpg"}}\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Postman:"})}),"\n",(0,r.jsx)("img",{src:"/docs/img/tutorials/ww3.png",alt:"ipfs",className:"responsive-pic",width:"700"}),"\n",(0,r.jsxs)(n.p,{children:["To confirm that the file has also been uploaded, we can check our ",(0,r.jsx)(n.code,{children:"bucket"})," folder to see a new ",(0,r.jsx)(n.code,{children:"test-forever.jpg"})," created. We can also create a server on port 5002 to see that our file is uploaded by running the following:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"# /node-filebase-ipfs-uploader\nnpx http-server -p 5002 bucket;\n\n# Expected Output\n# npx: installed 39 in 3.094s\n# Starting up http-server, serving bucket\n# \n# http-server version: 14.1.1\n# \n# http-server settings: \n# CORS: disabled\n# Cache: 3600 seconds\n# Connection Timeout: 120 seconds\n# Directory Listings: visible\n# AutoIndex: visible\n# Serve GZIP Files: false\n# Serve Brotli Files: false\n# Default File Extension: none\n# \n# Available on:\n# http://127.0.0.1:5002\n# http://10.0.0.6:5002\n# Hit CTRL-C to stop the server\n\n"})}),"\n",(0,r.jsxs)(n.p,{children:["If we open up ",(0,r.jsx)(n.code,{children:"http://localhost:5002/test-forever.jpg"})," we can see in our browser that image is there."]}),"\n",(0,r.jsx)("img",{src:"/docs/img/tutorials/ww4.png",alt:"ipfs",className:"responsive-pic",width:"700"}),"\n",(0,r.jsx)(n.p,{children:"Now that we have the base setup for our local uploads, we\u2019re going to leverage multer-s3 to utilize Filebase\u2019s AWS S3 Client compatibility."}),"\n",(0,r.jsx)(n.h2,{id:"adding-filebase-ipfs-support",children:"Adding Filebase IPFS Support"}),"\n",(0,r.jsxs)(n.p,{children:["Before we can add the code, we\u2019ll need to create a new account at Filebase.com. Once an account has been created, we\u2019ll need to create a new ",(0,r.jsx)(n.strong,{children:"bucket"}),", with the ",(0,r.jsx)(n.strong,{children:"Storage Network"})," set to ",(0,r.jsx)(n.strong,{children:"IPFS (All data is public)"}),"."]}),"\n",(0,r.jsx)("img",{src:"/docs/img/tutorials/FmrYcQ1KI.png",alt:"ipfs",className:"responsive-pic",width:"700"}),"\n",(0,r.jsx)("img",{src:"/docs/img/tutorials/ww5.png",alt:"ipfs",className:"responsive-pic",width:"700"}),"\n",(0,r.jsx)("img",{src:"/docs/img/tutorials/ww6.png",alt:"ipfs",className:"responsive-pic",width:"700"}),"\n",(0,r.jsx)(n.p,{children:"Once we have our newly created bucket, we\u2019re going to take note of the name so that we can use it later, and then get our Acce
1ss Keys."}),"\n",(0,r.jsx)("img",{src:"/docs/img/tutorials/ww7.png",alt:"ipfs",className:"responsive-pic",width:"700"}),"\n",(0,r.jsxs)(n.p,{children:["Now that we have the values, we\u2019re going to create a dot environment file ",(0,r.jsx)(n.code,{children:".env"}),", but we\u2019ll create a template (.env.example) for it because we should never save our ",(0,r.jsx)(n.code,{children:".env"})," to our git repository."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'# /node-filebase-ipfs-uploader\n\necho "PORT=5001\\nNODE_ENV=development\\nFILEBASE_ACCESS_KEY=key\\nFILEBASE_SECRET_KEY=secret\\nFILEBASE_BUCKET=bucket\\nFILEBASE_REGION=us-east-1\\nFILE_SERVER_URL=http://localhost:5002" > .env.example;\ncp .env.example .env;\n'})}),"\n",(0,r.jsx)(n.p,{children:"In our copied .env we\u2019ll need to fill it out with the values we just got from Filebase."}),"\n",(0,r.jsxs)(n.p,{children:["File: ",(0,r.jsx)(n.code,{children:"./.env"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"PORT=5001\nNODE_ENV=development\nFILEBASE_ACCESS_KEY=<YOUR-FILEBASE-ACCESS-KEY>\nFILEBASE_SECRET_KEY=<YOUR-FILEBASE-SECRET-KEY>\nFILEBASE_BUCKET=<YOUR-FILEBASE-BUCKET-NAME>\nFILEBASE_REGION=us-east-1\nFILE_SERVER_URL=http://localhost:5002\n"})}),"\n",(0,r.jsxs)(n.p,{children:["What we want to do is create a way so that when we\u2019re running in ",(0,r.jsx)(n.code,{children:"development"})," mode the files upload to our local ",(0,r.jsx)(n.code,{children:"bucket"})," folder, but when we\u2019re in production mode, the files upload to IPFS with Filebase. To do this, we\u2019re going to take advantage of our ",(0,r.jsx)(n.code,{children:"NODE_ENV"})," and modify our ",(0,r.jsx)(n.code,{children:"upload"})," middleware to use a different multer configuration when the ",(0,r.jsx)(n.code,{children:"NODE_ENV"})," is set to production. More specifically, we\u2019ll be using an extension of ",(0,r.jsx)(n.code,{children:"multer"})," called ",(0,r.jsx)(n.code,{children:"multer-s3"})," that handles requests regularly to AWS S3 but because Filebase is an AWS S3 compatible service, we\u2019ll just configure it to point to Filebase."]}),"\n",(0,r.jsxs)(n.p,{children:["File: ",(0,r.jsx)(n.code,{children:"./src/app.ts"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-typescript",children:'// Imports\n// ========================================================\nimport { config } from "dotenv";\nimport express from "express";\nimport cors from "cors";\nimport multer from "multer";\nimport multerS3 from "multer-s3";\nimport { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";\n\n// ENV VARS\n// ========================================================\nconfig();\nconst NODE_ENV: string = process.env.NODE_ENV || "development";\nconst FILE_DEST: string = process.env.FILE_DEST || "bucket";\nconst FILE_SERVER_URL: string =\n process.env.FILE_SERVER_URL || "http://localhost:5002";\nconst FILEBASE_BUCKET = process.env.FILEBASE_BUCKET || "";\n// Configured AWS S3 Client For Filebase\nconst s3 = new S3Client({\n endpoint: "https://s3.filebase.com",\n region: process.env.FILEBASE_REGION || "",\n credentials: {\n accessKeyId: process.env.FILEBASE_ACCESS_KEY || "",\n secretAccessKey: process.env.FILEBASE_SECRET_KEY || "",\n },\n});\n\n// Init\n// ========================================================\n/**\n * Initial ExpressJS\n */\nconst app = express();\n\n// Middlewares\n// ========================================================\n/**\n * Allows for requests from other servers\n */\napp.use(cors());\n\n/**\n * Main uploader middleware that configures the final `destination` of the file and how the `filename` would be set once saved\n */\nconst upload =\n // If production use the s3 client\n NODE_ENV === "production"\n ? multer({\n storage: multerS3({\n s3: s3,\n bucket: FILEBASE_BUCKET,\n metadata: (_req, file, cb) => {\n cb(null, { fieldName: file.fieldname });\n },\n key: (_req, file, cb) => {\n cb(null, file.originalname);\n },\n }),\n })\n : multer({\n storage: multer.diskStorage({\n destination: (_req, file, callback) =>
1 {\n callback(null, FILE_DEST);\n },\n filename: (_req, file, callback) => {\n callback(null, file.originalname);\n },\n }),\n });\n\n// Endpoints / Routes\n// ========================================================\n/**\n * Main endpoint to verify that things are working and what environment mode it\'s running in\n */\napp.get("/", (_req, res) => res.send({ environment: NODE_ENV }));\n\n/**\n * Upload endpoint that accepts an input file field of `file`\n */\napp.post("/upload", upload.single("file"), async (req, res) => {\n const responseData = {\n file: req.file?.originalname,\n url: `${FILE_SERVER_URL}/${req.file?.originalname}`,\n };\n\n // If production retrieve file data to get the ipfs CID\n if (NODE_ENV === "production") {\n const commandGetObject = new GetObjectCommand({\n Bucket: FILEBASE_BUCKET,\n Key: req.file?.originalname,\n });\n const response = await s3.send(commandGetObject);\n responseData.url = `ipfs://${response.Metadata?.cid}`;\n }\n\n return res.json({ data: responseData });\n});\n\n// Exports\n// ========================================================\nexport default app;\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Now, in order to test it, we just need to create a different start script that passes a new ",(0,r.jsx)(n.code,{children:"NODE_ENV"}),". To do this, we\u2019ll need to modify our ",(0,r.jsx)(n.code,{children:"package.json"})]}),"\n",(0,r.jsxs)(n.p,{children:["File: ",(0,r.jsx)(n.code,{children:"./package.json"})]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'{\n "name": "node-filebase-ipfs-uploader",\n "version": "1.0.0",\n "main": "index.js",\n "license": "MIT",\n "scripts": {\n "dev": "nodemon src/server.ts",\n "start": "export NODE_ENV=production && tsc && node build/server.js"\n },\n "dependencies": {\n "@aws-sdk/client-s3": "^3.113.0",\n "@types/cors": "^2.8.12",\n "@types/express": "^4.17.13",\n "@types/multer-s3": "^3.0.0",\n "@types/node": "^18.0.0",\n "aws-sdk": "^2.1157.0",\n "cors": "^2.8.5",\n "dotenv": "^16.0.1",\n "express": "^4.18.1",\n "multer": "^1.4.5-lts.1",\n "multer-s3": "^3.0.1",\n "typescript": "^4.7.4"\n },\n "devDependencies": {\n "nodemon": "^2.0.16",\n "ts-node": "^10.8.1"\n }\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"Now when we run the following we should see our server running on port 8080."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"# /node-filebase-ipfs-uploader\n\nyarn start;\n\n# Expected Output\n# yarn run v1.22.18\n# $ export NODE_ENV=production && tsc && node build/server.js\n# Listening on PORT 8080\n# Environment: production\n"})}),"\n",(0,r.jsx)(n.p,{children:"If we try uploading our file again with curl or Postman with the new address, we should get a different result."}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Curl:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'# /node-filebase-ipfs-uploader\n\ncurl --location --request POST \'http://localhost:8080/upload\' \\\n--form \'file=@"/full/path/to/node-filebase-ipfs-uploader/test/test-forever.jpg"\';\n\n# Expected Output\n# {"data":{"file":"test-forever.jpg","url":"ipfs://QmY8UXb7Nka5VWkXXRAMC1DQtamc1s8xzcjoQY6GaEbTdn"}}\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Postman:"})}),"\n",(0,r.jsx)("img",{src:"/docs/img/tutorials/ww8.png",alt:"ipfs",className:"responsive-pic",width:"700"}),"\n",(0,r.jsx)(n.p,{children:"Seeing that IPFS url, we can go to it directly and see that it\u2019s actually persisted on the decentralized storage."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"https://ipfs.filebase.io/ipfs/bafybeierozrtdlir5ywdguah573ga25a7tzzhqeh7kvlvfx2y2wqymdj7u\n"})}),"\n",(0,r.jsx)("img",{src:"/docs/img/tutorials/ww9.png",alt:"ipfs",className:"responsive-pic",width:"700"}),"\n",(0,r.jsx)(n.p,{children:"There you have it, we built a Backend IPFS File Uploader with Filebase."}),"\n",(0,r.jsx)(n.h3,{id:"whats-next",children:"What\u2019s Next?"}),"\n",(0,r.jsx)(n.p,{children:"Now that you have the backend, the next step is to build a frontend that communicates with the backend."}),"\n",(0,r.jsx)(n.p,{children:"Another aspect that could be worked on is the deployment of the backend to either a service like Digital Ocean with Docker, or Netlify with Edge Functions."})]})}function p(e={}){let{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},28453(e,n,t){t.d(n,{R:()=>o,x:()=>a});var s=t(96540);let r={},i=s.createContext(r);function o(e){let n=s.useContext(i);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),s.createElement(i.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.