Pular para o conteúdo principal

Upload

Enviar arquivos para a API e salvar em pasta ou na base de dados.

Introdução

Quando precisamos enviar arquivos para os serviços da API REST temos várias formas de realizar este desenvolvimento no Netuno.

Normalmente os arquivos são enviados no corpo do pedido HTTP estruturados no formato multipart, é o procedimento mais comum no frontend, por exemplo quando preenchemos um formulário e tem aquele campo para escolher um arquivo.

Em alternativa podemos enviar os bytes dos arquivos codificados em Base64 como valor em um objeto JSON, quando queremos uma API REST 100% JSON.

Arquivos

Através do _req.file podemos passar o nome do campo e assim obter o objeto do arquivo que foi enviado, veja como:

const meuArquivo = _req.file("arquivo")

Ou seja, o _req tem todos os dados que recebemos no corpo do pedido HTTP.

E através do método file permite obter o objeto que representa o arquivo que foi enviado.

O valor obtido poderá ser null caso não haja nenhum arquivo.

Imagens

Quando recebemos imagens é comum haver a necessidade para redimenciona-la, para garantir uniformidade e para reduzir o espaço ocupado pelas imagens.

const meuArquivo = _req.getFile("imagem")
if (meuArquivo != null
&& (meuArquivo.isExtension("jpeg")
|| meuArquivo.isExtension("jpg")
|| meuArquivo.isExtension("png")
)) {
const minhaImagem = _image.init(meuArquivo)
.resize(200, 200)
.file(meuArquivo.baseName() +".jpg", "jpeg")
_header.contentTypeJPG()
_out.copy(minhaImagem)
}

Com o recurso de programação low-code poliglota Image, podemos manipular imagens, cortar, redimencionar, criar novas, etc.

Como Base64 no JSON

Para realizar o upload de um arquivo no JSON enviado para o serviço, podemos definir uma propriedade com o valor:

data:image/png;base64,...

Onde nos ... segue a sequência dos bytes do arquivo codificados em Base64.

Sendo que o tipo do arquivo vai a seguir ao data, por exemplo:

  • PNG: image/png.
  • JPG: image/jpeg
  • TXT: text/plain
  • PDF: application/pdf
  • XLSX: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

Veja o exemplo de um arquivo de uma imagem PNG pequena sendo enviada no JSON para um serviço:

{
"file": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAFIGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpDb2xvclNwYWNlPSIxIgogICBleGlmOlBpeGVsWERpbWVuc2lvbj0iMyIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjMiCiAgIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiCiAgIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMyIKICAgdGlmZjpJbWFnZVdpZHRoPSIzIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSI3Mi8xIgogICB0aWZmOllSZXNvbHV0aW9uPSI3Mi8xIgogICB4bXA6TWV0YWRhdGFEYXRlPSIyMDI1LTEyLTI5VDE5OjEyOjI3WiIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjUtMTItMjlUMTk6MTI6MjdaIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHhtcE1NOmFjdGlvbj0icHJvZHVjZWQiCiAgICAgIHhtcE1NOnNvZnR3YXJlQWdlbnQ9IkFmZmluaXR5IERlc2lnbmVyIDEuMTAuNSIKICAgICAgeG1wTU06d2hlbj0iMjAyNS0wMi0yM1QyMjoxNDozM1oiLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0icHJvZHVjZWQiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFmZmluaXR5IDMuMC4xIgogICAgICBzdEV2dDp3aGVuPSIyMDI1LTEyLTI5VDE5OjEyOjI3WiIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+UGhcxwAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZG7SwNBEIe/JEqCRhS0sFAIEq3iG4I2ggmigkiIEXw1yZmHkMdxlyDBVrANKIg2vgr9C7QVrAVBUQSx1lbRRuWc84QEMbPMzre/3Rl2Z8EeSSsZvaYPMtm8Fh4PeObmFzzOZ9y004yL3qiiq6Oh0BRV7f0Wmxmvu81a1c/9a/XLcV0Bm0t4RFG1vPCE8NRqXjV5S7hFSUWXhU+EfZpcUPjG1GMWP5mctPjTZC0SDoK9SdiTrOBYBSspLSMsL8ebSReU3/uYL3HHs7MzEjvE29AJM04AD5OMEcRPP8My++lmgB5ZUSW/7yd/mpzkKjKrFNFYIUmKPD5RC1I9LjEhelxGmqLZ/7991RODA1Z1dwBqHw3jtROcm/BVMoyPA8P4OgTHA5xny/m5fRh6E71U1rx70LgOpxdlLbYNZxvQeq9GteiP5BC3JxLwcgwN89B8BXWLVs9+9zm6g8iafNUl7OxCl5xvXPoGe8Rn71E5kqUAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAySURBVAiZAScA2P8BJYrEogr//l3h5+qlBP4HCF3n/AAA/wP7WwLu7PKk+eHWAPzWyKSmfRbbIj23ZQAAAABJRU5ErkJggg=="
}

Podemos obter o arquivo enviado da seguinte forma:

const meuArquivo = _req.getFile("file")

if (meuArquivo) {
_out.json(
_val.map()
.set("result", true)
.set("name", meuArquivo.name())
.set("sizeBytes", meuArquivo.size())
.set("sizeKB", meuArquivo.sizeKB())
)
} else {
_out.json(
_val.map()
.set('result', false)
)
}

Exemplo do output gerado pelo serviço acima:

{
"name": "file.png",
"result": true,
"size": 1849,
"sizeKB": 1
}

Objeto

Se o arquivo estiver numa estrutura interna do JSON dentro do subobjeto:

{
"person": {
"file": "data:image/png;base64,..."
}
}

Pode ser obtido da seguinte forma:

const meuArquivo = _req.getValues("person").getFile("avatar")

Array

Caso o arquivo estiver numa estrutura interna do JSON dentro do array:

{
"files": [
"data:image/png;base64,..."
]
}

Pode ser obtido da seguinte forma:

const meuArquivo = _req.getValues("files").getFile(0)

OpenAPI

O Netuno suporta a integração com a OpenAPI e utiliza a definição da OpenAPI para validar o pedido HTTP.

Mais sobre a OpenAPI no Netuno.

Com o tipo file o Netuno valida se o valor começa com o formato de arquivo no JSON, por exemplo:

  • data:image/png;base64,...

Para validar se está recebendo um arquivo em uma propriedade do objeto JSON:

post.in.json
{
"summary": "Upload de Arquivo",
"description": "Serviço que recebe um arquivo no objeto JSON.",
"type": "object",
"properties": {
"file": {
"type": "file"
}
},
"required": [
"file"
]
}

Para validar se está recebendo uma lista de arquivos em array no JSON:

post.in.json
{
"summary": "Upload de Arquivos",
"description": "Serviço que recebe uma lista de arquivos em array no JSON.",
"properties": {
"files": {
"type": "array",
"items": {
"type": "file"
}
}
},
"required": [
"files"
]
}

Salvar o Arquivo

Normalmente quando recebemos um arquivo no serviço precisamos salvá-lo, como por exemplo:

  • Base de dados: Arquivo relacionado a um registro, como de um usuário.
  • Storage: Pasta interna da aplicação que serve para gerir arquivos no geral.
  • Pasta: Colocar o arquivo em uma pasta qualquer do sistema.

Base de Dados

Na gestão dos campos dos formulários, o Netuno suporta o tipo image e file, sendo:

  • Image: Arquivos de imagem no geral, JPG e PNG.
  • File: Qualquer tipo de arquivo.

Imagine que tem um campo de Imagem no formulário Produto, podemos fazer salvar o arquivo enviado via upload da seguinte forma:

Exemplo utilizando o DB Form:

Repare na utilização do _db.form:

services/produto/post.js
const produtoUid = _req.getString("uid")
const produtoImagem = _req.getFile("imagem")

if (produtoImagem == null) {
_header.status(400)
_out.json(
_val.map()
.set("result", false)
.set("error", "imagem-obrigatória")
)
_exec.stop()
}

const dbResultado = _db.form("produto")
.set("imagem", produtoImagem)
.where(_db.where("uid").equals(produtoUid))
.update()

if (dbResultado.getInt("produto") == 1) {
_out.json(
_val.map()
.set("result", true)
)
} else {
_header.status(404)
_out.json(
_val.map()
.set("result", false)
.set("error", "uid-produto-não-existe")
)
}

Exemplo utilizando o update clássico:

Repare na utilização do _db.update:

services/produto/post.js
const produtoUid = _req.getString("uid")
const produtoImagem = _req.getFile("imagem")

if (produtoImagem == null) {
_header.status(400)
_out.json(
_val.map()
.set("result", false)
.set("error", "imagem-obrigatória")
)
_exec.stop()
}

const dbResultado = _db.update(
"produto",
produtoUid,
_val.map()
.set("imagem", produtoImagem)
)

if (dbResultado == 1) {
_out.json(
_val.map()
.set("result", true)
)
} else {
_header.status(404)
_out.json(
_val.map()
.set("result", false)
.set("error", "uid-produto-não-existe")
)
}

Download do Arquivo da Base de Dados

Para apresentar o arquivo armazenado devemos criar um serviço que fornece os bytes do arquivo.

Sendo que os arquivos salvados em base de dados, como no exemplo acima, o arquivo em si é colocado no storage da aplicação e na base de dados fica armazenado apenas o nome final do arquivo.

O storage da aplicação é uma pasta destinada a armazenar e organizar estruturas de arquivos genéricos.

Portanto, no exemplo acima os arquivos de base de dados do formulário Produto e do campo Imagem ficam armazenados em:

  • storage/database/produto/imagem/*

O Netuno automaticamente gere o nome do arquivo armazenado para evitar nomes de arquivos repetidos.

Para obter o arquivo, pode ser criado um serviço que apresenta o arquivo de imagem do produto, por exemplo:

services/produto/imagem/get.js
const produtoUid = _req.getString("uid")

// DB Form:
const dbProduto = _db.form("produto")
.where(_db.where("uid").equals(produtoUid))
.first()
// Alternativa com DB Clássico:
// const dbProduto = _db.get("produto", produtoUid)

if (dbProduto == null) {
_header.status(404)
_out.json(
_val.map()
.set("result", false)
.set("error", "produto-nao-encontrado")
)
_exec.stop()
}

const dbProdutoImagemNome = dbProduto.getString("imagem")

const storageProdutoImagemArquivo = _storage.database(
"produto",
"imagem",
dbProdutoImagemNome
)

if (storageProdutoImagemArquivo.extension() == "jpg" || storageProdutoImagemArquivo.extension() == "jpeg") {
_header.contentTypeJPG()
} else {
_header.contentTypePNG()
}

_header.noCache()

_out.copy(storageProdutoImagemArquivo.inputStream())

Repare que o recurso _storage é utilizado para obter o arquivo:

_storage.database(
"produto", // Nome do formulário/tabela.
"imagem", // Nome do campo/coluna.
dbProdutoImagemNome // Nome do arquivo.
)

Repare que na base de dados fica armazenado apenas o nome do arquivo, sendo que através do _storage.database podemos obter o arquivo em si.

Storage

Podemos salvar um arquivo diretamente no storage da aplicação.

Para não misturar com os arquivos de base de dados, devemos utilizar a pasta de filesystem, que tem a finalidade de armazenar arquivos genéricos que não estão associados na base de dados.

Para receber o arquivo, neste exemplo é demonstrado como um arquivo enviado no serviço POST é armazenado diretamente no storage:

services/arquivo/post.js
const arquivo = _req.getFile("arquivo")
_storage.filesystem("server", "upload").ensurePath()
arquivo.save(_storage.filesystem("server", "upload", arquivo.name()))

O arquivo fica armazenado na pasta storage da aplicação, em:

  • storage/filesystem/server/upload/*

Repare que o recurso _storage é utilizado para obter o caminho de pasta e de arquivo:

_storage.filesystem(
"server", // Nome da subpasta no filesystem.
"upload", // Nome da outra subpasta.
arquivo.name() // Nome final do arquivo.
)

Veja mais sobre a documentação do Storage.

Para fazer o download do arquivo, neste exemplo é demonstrado como os bytes do arquivo podem ser obtidos através do serviço GET:

services/arquivo/get.js
const arquivoNome = _req.getString("arquivo")

const arquivo = _storage.filesystem(
"server",
"upload",
arquivoNome
).file()

if (arquivo.exists() == false) {
_header.status(404)
_out.json(
_val.map()
.set("result", false)
.set("error", "arquivo-nao-existe")
)
_exec.stop()
}

_header.downloadFile(arquivo.name())
_out.copy(arquivo.input())

Pasta

Podemos salvar um arquivo diretamente em qualquer pasta do computador.

Há dois recursos que são muito úteis nestes casos:

  • _app - Podemos pegar qualquer pasta ou arquivo na aplicação.
  • _os - Podemos pegar qualquer pasta ou arquivo no computador.

Veja a documentação dos respectivos recursos App e OS, ambos os recursos tem os métodos folder e file, que permitem indicar qual o caminho da pasta ou do arquivo respectivamente.

Para receber o arquivo, neste exemplo é demonstrado como um arquivo enviado no serviço POST é armazenado diretamente em uma pasta na aplicação:

services/arquivo/post.js
const arquivo = _req.getFile("arquivo")
const pasta = _app.folder("temp")
if (pasta.exists() == false) {
pasta.mkdir()
}
arquivo.save(_app.file(`temp/${arquivo.name()}`))

O código acima vai criar a pasta temp dentro da aplicação e salvar o arquivo recebido dentro desta pasta.

Para fazer o download do arquivo, neste exemplo é demonstrado como os bytes do arquivo podem ser obtidos através do serviço GET:

services/arquivo/get.js
const arquivoNome = _req.getString("arquivo")
const arquivo = _app.file(`temp/${arquivoNome}`)

if (arquivo.exists() == false) {
_header.status(404)
_out.json(
_val.map()
.set("result", false)
.set("error", "arquivo-nao-existe")
)
_exec.stop()
}

_header.downloadFile(arquivo.name())
_out.copy(arquivo.input())

Conclusão

Como pode ser visto podemos integrar o upload de arquivos nos serviços da API REST, inclusive integrado com a OpenAPI.

Podemos salvar o arquivo na base de dados, dentro da pasta storage da aplicação ou em qualquer pasta do computador.

Veja a documentação dos recursos utilizados neste tutorial que permitem manipular os arquivos:

Em base de dados podemos salvar imagens em campos do tipo image, e podemos salvar qualquer tipo de arquivo em campos do tipo file, como salvamos ou obtemos arquivos são da mesma forma em qualquer caso, seja imagem ou arquivo genérico.