WEB3DEV

Cover image for Crie um Uploader de Arquivos IPFS com o NodeJS de Backend Usando o Filebase
Banana Labs
Banana Labs

Posted on

Crie um Uploader de Arquivos IPFS com o NodeJS de Backend Usando o Filebase

IPFS & Filebase

Se você quiser entender um pouco melhor o IPFS, confira o último artigo escrito para entender e fazer upload de arquivos para o IPFS com Filebase. Este tutorial se concentrará na criação de uma API de back-end com Node que permite o upload para IPFS usando Filebase.

Requisitos

Antes de começarmos, precisamos garantir que você tenha o seguinte instalado localmente em seu computador.

Configuração do projeto Node

O primeiro passo é configurar nosso projeto do zero com Express & Typescript.

mkdir node-filebase-ipfs-uploader;
cd node-filebase-ipfs-uploader;
mkdir bucket; # Pasta que usaremos para testes locais
touch bucket/.gitkeep; # Para garantir que mantemos a pasta e não seu arquivo
mkdir src; # Onde nosso código ficará
echo "16.15.1" > .nvmrc;
nvm install; # ignore se você já tiver o Node 16.15.1 instalado
yarn init -y;
git init;
echo "node_modules\n.env\nbucket/*\n\!bucket/.gitkeep\nbuild\n*.log" > .gitignore;
yarn add aws-sdk cors express dotenv multer multer-s3 typescript @aws-sdk/client-s3 @types/cors @types/express @types/multer-s3 @types/node;
yarn add -D nodemon ts-node;
./node_modules/.bin/tsc --init; # gera nosso arquivo tsconfig.json
Enter fullscreen mode Exit fullscreen mode

Agora que temos nosso projeto configurado, vamos fazer uma modificação em nosso arquivo de configuração TypeScript para ajustar a pasta de saída para ./build.

Arquivo: ./tsconfig.json

{
  "compilerOptions": {
    /* Visite https://aka.ms/tsconfig para ler mais sobre este arquivo */

    /* Projects */
    // "incremental": true,                              /* Salve arquivos .tsbuildinfo para permitir a compilação incremental de projetos. */
    // "composite": true,                                /* Habilite restrições que permitem que um projeto TypeScript seja usado com referências de projeto. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /*Especifique o caminho para o arquivo de compilação incremental .tsbuildinfo. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Desative a preferência por arquivos de origem em vez de arquivos de declaração ao fazer referência a projetos compostos. */
    // "disableSolutionSearching": true,                 /* opte por excluir um projeto da verificação de referência de vários projetos durante a edição. */
    // "disableReferencedProjectLoad": true,             /* Reduza o número de projetos carregados automaticamente pelo TypeScript. */

    /* Language and Environment */
    "target": "es2016" /*Defina a versão da linguagem JavaScript para o JavaScript emitido e inclua declarações de biblioteca compatíveis. */,
    // "lib": [],                                        /* Especifique um conjunto de arquivos de declaração de biblioteca compactados que descrevem o ambiente de tempo de execução de destino. */
    // "jsx": "preserve",                                /* Especifique qual código JSX é gerado.*/
    // "experimentalDecorators": true,                   /* Ative o suporte experimental para decoradores de rascunho do estágio 2 do TC39.*/
    // "emitDecoratorMetadata": true,                    /* Emite metadados de tipo de design para declarações decoradas em arquivos de origem. */
    // "jsxFactory": "",                                 /* Especifique a função de fábrica JSX usada ao direcionar a emissão do React JSX, por exemplo 'React.createElement' ou 'h'. */
    // "jsxFragmentFactory": "",                         /* Especifique a referência de fragmento JSX usada para fragmentos ao direcionar a emissão do React JSX, por exemplo 'React.Fragment' ou 'Fragmento'. */
    // "jsxImportSource": "",                            /* Especifique o especificador de módulo usado para importar as funções de fábrica JSX ao usar 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Especifique o objeto chamado para 'createElement'. Isso se aplica apenas ao direcionar a emissão de JSX 'reagir'. */
    // "noLib": true,                                    /* Desative a inclusão de quaisquer arquivos de biblioteca, incluindo o padrão lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emita campos de classe compatíveis com o padrão ECMAScript.*/
    // "moduleDetection": "auto",                        /* Controle qual método é usado para detectar arquivos JS em formato de módulo. */

    /* Modules */
    "module": "commonjs" /* Especifique qual código de módulo é gerado. */,
    // "rootDir": "./",                                  /* Especifique a pasta raiz em seus arquivos de origem.*/
    "moduleResolution": "node" /* Especifique como o TypeScript procura um arquivo de um determinado especificador de módulo. */,
    // "baseUrl": "./",                                  /* Especifique o diretório base para resolver nomes de módulos não relativos. */
    // "paths": {},                                      /* Especifique um conjunto de entradas que mapeiam novamente as importações para locais de pesquisa adicionais. */
    // "rootDirs": [],                                   /* Permita que várias pastas sejam tratadas como uma ao resolver módulos. */
    // "typeRoots": [],                                  /* Especifique várias pastas que agem como './node_modules/@types'. */
    // "types": [],                                      /* Especifique nomes de pacotes de tipo a serem incluídos sem serem referenciados em um arquivo de origem. */
    // "allowUmdGlobalAccess": true,                     /* Permita o acesso a globais UMD a partir de módulos. */
    // "moduleSuffixes": [],                             /* Lista de sufixos de nome de arquivo para pesquisar ao resolver um módulo.*/
    // "resolveJsonModule": true,                        /* Ative a importação de arquivos .json. */
    // "noResolve": true,                                /* Não permita que 'import's, 'require's ou '<reference>'s expandam o número de arquivos que o TypeScript deve adicionar a um projeto. */

    /* JavaScript Support */
    // "allowJs": true,                                  /* Permita que arquivos JavaScript façam parte do seu programa. Use a opção 'checkJS' para obter erros desses arquivos. */
    // "checkJs": true,                                  /* Habilite o relatório de erros em arquivos JavaScript com verificação de tipo.*/
    // "maxNodeModuleJsDepth": 1,                        /* Especifique a profundidade máxima da pasta usada para verificar arquivos JavaScript de 'node_modules'. Aplicável somente com 'allowJs'. */

    /* Emit */
    // "declaration": true,                              /* Gere arquivos .d.ts a partir de arquivos TypeScript e JavaScript em seu projeto. */
    // "declarationMap": true,                           /* Crie mapas de origem para arquivos d.ts.*/
    // "emitDeclarationOnly": true,                      /*Apenas gere arquivos d.ts e não arquivos JavaScript. */
    "sourceMap": true /* Crie arquivos de mapa de origem para arquivos JavaScript emitidos. */,
    // "outFile": "./",                                  /*Especifique um arquivo que agrupe todas as saídas em um arquivo JavaScript. Se 'declaração' for verdadeiro, também designa um arquivo que agrupa toda a saída .d.ts. */
    "outDir": "./build" /* Especifique uma pasta de saída para todos os arquivos emitidos. */,
    // "removeComments": true,                           /* Desative a emissão de comentários.*/
    // "noEmit": true,                                   /* Desative a emissão de arquivos de uma compilação. */
    // "importHelpers": true,                            /* Permita a importação de funções auxiliares de tslib uma vez por projeto, em vez de incluí-las por arquivo. */
    // "importsNotUsedAsValues": "remove",               /* Especifique o comportamento de emissão/verificação para importações que são usadas apenas para tipos. */
    // "downlevelIteration": true,                       /* Emita JavaScript mais compatível, mas detalhado e com menos desempenho para iteração. */
    // "sourceRoot": "",                                 /* Especifique o caminho raiz para que os depuradores localizem o código-fonte de referência. */
    // "mapRoot": "",                                    /* Especifique o local onde o depurador deve localizar os arquivos de mapa em vez dos locais gerados. */
    // "inlineSourceMap": true,                          /* Inclua arquivos sourcemap dentro do JavaScript emitido. */
    // "inlineSources": true,                            /* Inclua o código-fonte nos mapas de origem dentro do JavaScript emitido.*/
    // "emitBOM": true,                                  /* Emita uma marca de ordem de byte UTF-8 (BOM) no início dos arquivos de saída. */
    // "newLine": "crlf",                                /* Defina o caractere de nova linha para emitir arquivos. */
    // "stripInternal": true,                            /*Desabilite a emissão de declarações que tenham '@internal' em seus comentários JSDoc. */
    // "noEmitHelpers": true,                            /*Desative a geração de funções auxiliares personalizadas como '__extends' na saída compilada.*/
    // "noEmitOnError": true,                            /* Desative a emissão de arquivos se algum erro de verificação de tipo for relatado.*/
    // "preserveConstEnums": true,                       /* Desabilite a remoção das declarações 'const enum' no código gerado. */
    // "declarationDir": "./",                           /* Especifique o diretório de saída para arquivos de declaração gerados.*/
    // "preserveValueImports": true,                     /* Preserve valores importados não utilizados na saída JavaScript que, de outra forma, seriam removidos. */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* Certifique-se de que cada arquivo possa ser transpilado com segurança sem depender de outras importações. */
    // "allowSyntheticDefaultImports": true,             /* Permita 'importar x de y' quando um módulo não tiver uma exportação padrão.*/
    "esModuleInterop": true /* Emita JavaScript adicional para facilitar o suporte à importação de módulos CommonJS. Isso habilita 'allowSyntheticDefaultImports' para compatibilidade de tipo.*/,
    // "preserveSymlinks": true,                         /* Desative a resolução de links simbólicos para seu caminho real. Isso se correlaciona com o mesmo sinalizador em node. */
    "forceConsistentCasingInFileNames": true /* Assegure-se de que as maiúsculas e minúsculas estejam corretas nas importações.*/,

    /* Type Checking */
    "strict": true /* Habilite todas as opções rígidas de verificação de tipo. */,
    // "noImplicitAny": true,                            /* Habilite o relatório de erros para expressões e declarações com um tipo 'qualquer' implícito. */
    // "strictNullChecks": true,                         /*Ao verificar o tipo, leve em consideração 'nulo' e 'indefinido'.*/
    // "strictFunctionTypes": true,                      /* Ao atribuir funções, verifique se os parâmetros e os valores de retorno são compatíveis com subtipos. */
    // "strictBindCallApply": true,                      /* Verifique se os argumentos dos métodos 'bind', 'call' e 'apply' correspondem à função original. */
    // "strictPropertyInitialization": true,             /* Verifique as propriedades de classe que são declaradas, mas não definidas no construtor. */
    // "noImplicitThis": true,                           /* Habilite o relatório de erros quando 'this' receber o tipo 'any'. */
    // "useUnknownInCatchVariables": true,               /* Variáveis padrão da cláusula catch como 'unknown' em vez de 'any'. */
    // "alwaysStrict": true,                             /* Certifique-se de que 'use strict' seja sempre emitido.*/
    // "noUnusedLocals": true,                           /* Habilite o relatório de erros quando as variáveis locais não forem lidas. */
    // "noUnusedParameters": true,                       /* Gera um erro quando um parâmetro de função não é lido. */
    // "exactOptionalPropertyTypes": true,               /* Interprete os tipos de propriedade opcionais conforme escritos, em vez de adicionar 'indefinido'. */
    // "noImplicitReturns": true,                        /* Habilite o relatório de erros para codepaths que não retornam explicitamente em uma função. */
    // "noFallthroughCasesInSwitch": true,               /* Habilite o relatório de erros para casos de fallthrough em instruções switch. */
    // "noUncheckedIndexedAccess": true,                 /*Adicione 'indefinido' a um tipo quando acessado usando um índice. */
    // "noImplicitOverride": true,                       /* Certifique-se de que os membros de substituição em classes derivadas sejam marcados com um modificador de substituição. */
    // "noPropertyAccessFromIndexSignature": true,       /* Aplique o uso de acessadores indexados para chaves declaradas usando um tipo indexado.*/
    // "allowUnusedLabels": true,                        /* Desative o relatório de erros para rótulos não utilizados. */
    // "allowUnreachableCode": true,                     /* Desative o relatório de erros para código inacessível.*/

    /* Completeness */
  // "skipDefaultLibCheck": true,                      /* Ignore os arquivos .d.ts de verificação de tipo que estão incluídos no TypeScript. */
    "skipLibCheck": true /* Ignore a verificação de tipo de todos os arquivos .d.ts. */
  }
}
Enter fullscreen mode Exit fullscreen mode

Criando Endpoints Iniciais Do Servidor

Agora que temos a configuração inicial do projeto e a configuração dos arquivos, vamos criar dois novos arquivos que hospedam os endpoints e o servidor, respectivamente.

O primeiro arquivo são os endpoints e nossas configurações iniciais do servidor Express com um único endpoints get na raiz para verificar se o servidor está funcionando.

Arquivo: ./src/app.ts

// Imports
// ========================================================
import { config } from "dotenv";
import express from "express";
import cors from "cors";

// ENV VARS
// ========================================================
config();

const NODE_ENV: string = process.env.NODE_ENV || "development";

// Init
// ========================================================
/**
 * Initial ExpressJS
 */
const app = express();
// Middlewares
// ========================================================
/**
 * Permite solicitações de outros servidores
 */
app.use(cors());

// Endpoints / Routes
// ========================================================
/**
 * Endpoint principal para verificar se as coisas estão funcionando e em qual modo de ambiente está sendo executado
 */
app.get("/", (_req, res) => res.send({ environment: NODE_ENV }));

// Exports
// ========================================================
export default app;
Enter fullscreen mode Exit fullscreen mode

Nosso segundo arquivo importa os endpoints e os executa em um servidor específico. Normalmente, você pode ter visto ambos nos mesmos arquivos, mas para testes (abordados em outro artigo), geralmente é mais fácil executar testes de unidade e integração sem que o servidor execute questões separadas.

Arquivo: ./src/server.ts

// Imports
// ========================================================
import app from "./app";
import { config } from "dotenv";

// ENV VARS
// ========================================================
config();
const NODE_ENV: string = process.env.NODE_ENV || "development";
const PORT: number =
  NODE_ENV === "production" ? 8080 : parseInt(process.env.PORT || "5001", 10);

// Server
// ========================================================
app.listen(PORT, () =>
  console.log(`Listening on PORT ${PORT}\nEnvironment: ${NODE_ENV}`)
);
Enter fullscreen mode Exit fullscreen mode

Em seguida, vamos facilitar para nós mesmos adicionando um comando de script ao nosso arquivo package.json, para que possamos executar yarn dev

Arquivo: ./package.json

{
  "name": "node-filebase-ipfs-uploader",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "nodemon src/server.ts"
  },
  "dependencies": {
    "@aws-sdk/client-s3": "^3.113.0",
    "@types/cors": "^2.8.12",
    "@types/express": "^4.17.13",
    "@types/multer-s3": "^3.0.0",
    "@types/node": "^18.0.0",
    "aws-sdk": "^2.1157.0",
    "cors": "^2.8.5",
    "dotenv": "^16.0.1",
    "express": "^4.18.1",
    "multer": "^1.4.5-lts.1",
    "multer-s3": "^3.0.1",
    "typescript": "^4.7.4"
  },
  "devDependencies": {
    "nodemon": "^2.0.16",
    "ts-node": "^10.8.1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Agora, se executar yarn dev e abrir nosso navegador em http://localhost:5001, veremos nosso servidor em execução.

# /node-filebase-ipfs-uploader
yarn dev;

# Expected Output
# $ nodemon src/server.ts
# [nodemon] 2.0.16
# [nodemon] to restart at any time, enter `rs`
# [nodemon] watching path(s): *.*
# [nodemon] watching extensions: ts,json
# [nodemon] starting `ts-node src/server.ts`
# Listening on PORT 5001
# Environment: development
Enter fullscreen mode Exit fullscreen mode

localhost 5001

Nosso servidor agora está funcionando, agora precisamos adicionar a funcionalidade de upload.

Adicionando upload de arquivo local para obter uma configuração de código de upload de arquivo, vamos aproveitar um pacote npm chamado multer. O Multer é um middleware que facilita a vida do desenvolvedor na hora de lidar com arquivos. Originalmente, é feito apenas para desenvolvimento local, mas há extensões adicionais que oferecem suporte ao AWS S3, que usaremos para o Filebase.

Vamos modificar nosso arquivo de endpoints original para incluir um novo endpoint de upload POST que utiliza multer.

Arquivo: ./src/app.ts

// Imports
// ========================================================
import { config } from "dotenv";
import express from "express";
import cors from "cors";
import multer from "multer";

// ENV VARS
// ========================================================
config();
const NODE_ENV: string = process.env.NODE_ENV || "development";
const FILE_DEST: string = process.env.FILE_DEST || "bucket";
const FILE_SERVER_URL: string =
  process.env.FILE_SERVER_URL || "http://localhost:5002";

// Init
// ========================================================
/**
 * Initial ExpressJS
 */
const app = express();

// Middlewares
// ========================================================
/**
 * Permite solicitações de outros servidores
 */
app.use(cors());

/**
 * Middleware principal do uploader que configura o `destino` final do arquivo e como o `nome do arquivo` seria definido uma vez salvo
 */
const upload = multer({
  storage: multer.diskStorage({
    destination: (_req, file, callback) => {
      callback(null, FILE_DEST);
    },
    filename: (_req, file, callback) => {
      callback(null, file.originalname);
    },
  }),
});

// Endpoints / Routes
// ========================================================
/**
 * Endpoint principal para verificar se as coisas estão funcionando e em qual modo de ambiente está sendo executado
 */
app.get("/", (_req, res) => res.send({ environment: NODE_ENV }));

/**
 * Endpoint de upload que aceita um campo de arquivo de entrada de `arquivo`
 */
app.post("/upload", upload.single("file"), (req, res) => {
  const responseData = {
    file: req.file?.originalname,
    url: `${FILE_SERVER_URL}/${req.file?.originalname}`,
  };

  return res.json({ data: responseData });
});

// Exports
// ========================================================
export default app;
Enter fullscreen mode Exit fullscreen mode

Enquanto o servidor está rodando, vamos fazer um upload.

Curl:

# /node-filebase-ipfs-uploader

curl --location --request POST 'http://localhost:5001/upload' \
--form 'file=@"/full/path/to/node-filebase-ipfs-uploader/test/test-forever.jpg"';

# Expected Output
# {"data":{"file":"test-forever.jpg","url":"http://localhost:5002/test-forever.jpg"}}
Enter fullscreen mode Exit fullscreen mode

Postman:

Postman

Para confirmar que o arquivo também foi carregado, podemos verificar nossa pasta bucket para ver um novo test-forever.jpg criado. Também podemos criar um servidor na porta 5002 para ver que nosso arquivo é carregado executando o seguinte:

# /node-filebase-ipfs-uploader
npx http-server -p 5002 bucket;

# Expected Output
# npx: installed 39 in 3.094s
# Starting up http-server, serving bucket
# 
# http-server version: 14.1.1
# 
# http-server settings: 
# CORS: disabled
# Cache: 3600 seconds
# Connection Timeout: 120 seconds
# Directory Listings: visible
# AutoIndex: visible
# Serve GZIP Files: false
# Serve Brotli Files: false
# Default File Extension: none
# 
# Available on:
#   http://127.0.0.1:5002
#   http://10.0.0.6:5002
# Hit CTRL-C to stop the server
Enter fullscreen mode Exit fullscreen mode

Se abrirmos http://localhost:5002/test-forever.jpg podemos ver em nosso navegador que a imagem está lá.

Agora que temos a configuração básica para nossos uploads locais, vamos aproveitar o multer-s3 para utilizar a compatibilidade do cliente AWS S3 do Filebase.

Adicionando Suporte Filebase IPFS

Antes de podermos adicionar o código, precisamos criar uma nova conta no Filebase.com. Depois que uma conta for criada, precisaremos criar um novo bucket, com a rede de armazenamento definida como IPFS (todos os dados são públicos).

Filebase

Filebase

Filebase

Assim que tivermos nosso bucket recém-criado, vamos anotar o nome para que possamos usá-lo mais tarde e, em seguida, obter nossas chaves de acesso.

chaves de acesso

Agora que temos os valores, vamos criar um arquivo de ambiente dot .env, mas vamos criar um modelo (.env.example) para ele porque nunca devemos salvar nosso .env em nosso repositório git.

# /node-filebase-ipfs-uploader

echo "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;
cp .env.example .env;
Enter fullscreen mode Exit fullscreen mode

Em nosso .env copiado, precisaremos preenchê-lo com os valores que acabamos de obter do Filebase.

Arquivo: ./.env

PORT=5001
NODE_ENV=development
FILEBASE_ACCESS_KEY=<YOUR-FILEBASE-ACCESS-KEY>
FILEBASE_SECRET_KEY=<YOUR-FILEBASE-SECRET-KEY>
FILEBASE_BUCKET=<YOUR-FILEBASE-BUCKET-NAME>
FILEBASE_REGION=us-east-1
FILE_SERVER_URL=http://localhost:5002
Enter fullscreen mode Exit fullscreen mode

O que queremos fazer é criar uma maneira para que, quando estivermos executando no modo de desenvolvimento, os arquivos sejam carregados em nossa pasta bucket local, mas quando estivermos no modo de produção, os arquivos sejam carregados no IPFS com o Filebase. Para fazer isso, vamos aproveitar nosso NODE_ENV e modificar nosso middleware de upload para usar uma configuração multer diferente quando o NODE_ENV estiver definido para produção. Mais especificamente, usaremos uma extensão do multer chamada multer-s3 que lida com solicitações regularmente para o AWS S3, mas como o Filebase é um serviço compatível com o AWS S3, vamos apenas configurá-lo para apontar para o Filebase.

Arquivo: ./src/app.ts

// Imports
// ========================================================
import { config } from "dotenv";
import express from "express";
import cors from "cors";
import multer from "multer";
import multerS3 from "multer-s3";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";

// ENV VARS
// ========================================================
config();
const NODE_ENV: string = process.env.NODE_ENV || "development";
const FILE_DEST: string = process.env.FILE_DEST || "bucket";
const FILE_SERVER_URL: string =
  process.env.FILE_SERVER_URL || "http://localhost:5002";
const FILEBASE_BUCKET = process.env.FILEBASE_BUCKET || "";
// Configured AWS S3 Client For Filebase
const s3 = new S3Client({
  endpoint: "https://s3.filebase.com",
  region: process.env.FILEBASE_REGION || "",
  credentials: {
    accessKeyId: process.env.FILEBASE_ACCESS_KEY || "",
    secretAccessKey: process.env.FILEBASE_SECRET_KEY || "",
  },
});

// Init
// ========================================================
/**
 * Initial ExpressJS
 */
const app = express();

// Middlewares
// ========================================================
/**
 * Permite solicitações de outros servidores
 */
app.use(cors());

/**
 * Middleware principal do uploader que configura o `destino` final do arquivo e como o `nome do arquivo` seria definido uma vez salvo
 */
const upload =
  // If production use the s3 client
  NODE_ENV === "production"
    ? multer({
        storage: multerS3({
          s3: s3,
          bucket: FILEBASE_BUCKET,
          metadata: (_req, file, cb) => {
            cb(null, { fieldName: file.fieldname });
          },
          key: (_req, file, cb) => {
            cb(null, file.originalname);
          },
        }),
      })
    : multer({
        storage: multer.diskStorage({
          destination: (_req, file, callback) => {
            callback(null, FILE_DEST);
          },
          filename: (_req, file, callback) => {
            callback(null, file.originalname);
          },
        }),
      });

// Endpoints / Routes
// ========================================================
/**
 * Endpoint principal para verificar se as coisas estão funcionando e em qual modo de ambiente está sendo executado
 */
app.get("/", (_req, res) => res.send({ environment: NODE_ENV }));

/**
 * Endpoint de upload que aceita um campo de arquivo de entrada de `arquivo`
 */
app.post("/upload", upload.single("file"), async (req, res) => {
  const responseData = {
    file: req.file?.originalname,
    url: `${FILE_SERVER_URL}/${req.file?.originalname}`,
  };

  // Se a produção recuperar os dados do arquivo para obter o CID do ipfs
  if (NODE_ENV === "production") {
    const commandGetObject = new GetObjectCommand({
      Bucket: FILEBASE_BUCKET,
      Key: req.file?.originalname,
    });
    const response = await s3.send(commandGetObject);
    responseData.url = `ipfs://${response.Metadata?.cid}`;
  }

  return res.json({ data: responseData });
});

// Exports
// ========================================================
export default app;
Enter fullscreen mode Exit fullscreen mode

Agora, para testá-lo, precisamos apenas criar um script de inicialização diferente que passe um novo NODE_ENV. Para fazer isso, precisaremos modificar nosso package.json

Arquivo: ./package.json

{
  "name": "node-filebase-ipfs-uploader",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "nodemon src/server.ts",
    "start": "export NODE_ENV=production && tsc && node build/server.js"
  },
  "dependencies": {
    "@aws-sdk/client-s3": "^3.113.0",
    "@types/cors": "^2.8.12",
    "@types/express": "^4.17.13",
    "@types/multer-s3": "^3.0.0",
    "@types/node": "^18.0.0",
    "aws-sdk": "^2.1157.0",
    "cors": "^2.8.5",
    "dotenv": "^16.0.1",
    "express": "^4.18.1",
    "multer": "^1.4.5-lts.1",
    "multer-s3": "^3.0.1",
    "typescript": "^4.7.4"
  },
  "devDependencies": {
    "nodemon": "^2.0.16",
    "ts-node": "^10.8.1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Agora, quando executamos o seguinte, devemos ver nosso servidor rodando na porta 8080.

# /node-filebase-ipfs-uploader

yarn start;

# Expected Output
# yarn run v1.22.18
# $ export NODE_ENV=production && tsc && node build/server.js
# Listening on PORT 8080
# Environment: production
Enter fullscreen mode Exit fullscreen mode

Se tentarmos carregar nosso arquivo novamente com curl ou Postman com o novo endereço, devemos obter um resultado diferente.

Curl:

# /node-filebase-ipfs-uploader

curl --location --request POST 'http://localhost:8080/upload' \
--form 'file=@"/full/path/to/node-filebase-ipfs-uploader/test/test-forever.jpg"';

# Saída Esperada
# {"data":{"file":"test-forever.jpg","url":"ipfs://QmY8UXb7Nka5VWkXXRAMC1DQtamc1s8xzcjoQY6GaEbTdn"}}
Enter fullscreen mode Exit fullscreen mode

Postman:

Postman

Ao ver o URL do IPFS, podemos acessá-lo diretamente e ver se ele realmente persiste no armazenamento descentralizado.

https://ipfs.filebase.io/ipfs/bafybeierozrtdlir5ywdguah573ga25a7tzzhqeh7kvlvfx2y2wqymdj7u
Enter fullscreen mode Exit fullscreen mode

Aí está, construímos um Uploader de arquivos IPFS de back-end com Filebase.

Qual é o próximo?

Agora que você tem o back-end, a próxima etapa é criar um front-end que se comunique com o back-end.

Outro aspecto que poderia ser trabalhado é a implantação do back-end para um serviço como Digital Ocean com Docker ou Netlify com Edge Functions.


Esse artigo é uma tradução feita por @bananlabs. Você pode encontrar o artigo original aqui

Top comments (0)