Hi ha dues propostes per a pujar (actualitzar-les també) unes entitats que porten vinculades a la imatge:
- [POST] /api/v1/imatge amb un cos html serialitzat tipus { desciption: “”, tags: [“”, “”,””] }
- [POST] /api/v1/image/{id}/upload amb un multipart/form-www image=@adreçaImatge
Amb:
- [POST] /api/v1/imatge amb un multipart/form-data description=”” ; tags=[“”,””,””]; image=@adreçaImatge
I aquí és on premia qui ha de fer ús de la API, quina utilització se li dona. El fet de treballar amb entitats sempre és com allò de que per a un martell tot són claus. Hauríem de separar:
- Amb quina recurrència es pujaran imatges o serà un fet únic?
- Les aplicacions clients volen crear l’entitat amb la imatge?
- Les aplicacions clients com processen els multipart/form-data?
De les tres preguntes, les dues primeres són de disseny i la tercera d’arquitectura. Per allò del que els backends poden viure a esquenes de la realitat si ofereixen unes portes definides. Són importants perquè poden encorsetar bastant el processament de les dades (no és el mateix un form què un body serialitzat).
Amb NodeJS tenim multer què a mode intermediari (middleware) ens pot emmagatzemar la imatge en un directori, i posar-nos les dades del form-data al body com a propietats. En el següent exemple es fa servir la segona opció:
routes.js
app.post("/v1/imatge", imatgeController.uploadImg, imatgeController.save);
imatgeController.js
const multer = require('multer');
const fs = require('fs');
const imageStorage = multer.diskStorage({
destination: function (req, file, cb) {
if (!fs.existsSync('./imageUploads')) {
fs.mkdirSync('./imageUploads');
}
cb(null, './imageUploads');
},
filename: function (req, file, cb) {
cb(null, file.originalname);
}
});
exports.uploadImg = multer({ storage: imageStorage }).single('image');
exports.save = async (req, res) => {
var entity = { description: req.body.description, image: req.file.filename, tags: req.body.tags };
...
}
El fet de que podem sobrecarregar amb diverses funcions les rutes també ens facilitarà altres tipus de processaments (autenticació, per exemple), però això ja és cosa més de NodeJS i ExpressJS.
La solució ens sobrecarrega bastant el què és la entitat: la carrega de imatge per un costat, què pot tenir més lògica (compressió, tractament, desplaçament a un lloc remot) i pot fer més pesat del que és en si el pujar unes dades per a una entitat.
Per a fer un punt comú entre client i servidor, el codi swagger.json descriurà els camps com a formData parant especial atenció en el type:
"/v1/imatges": {
"post": {
"tags": [
"imatges"
],
"summary": "Afegir una imatge",
"description": "Ruta per afegir una nova imatge",
"consumes": [
"image"
],
"parameters": [
{
"name": "imatge",
"in": "formData",
"description": "afegir nova imatge",
"required": "true",
"type": "file"
},
{
"name": "descripcio",
"in": "formData",
"description": "descripció de la imatge",
"required": "true",
"type": "string"
}
],
...
Per al client, fem l’exemple de un swagger tipificat i l’utilitzem en un client, en aquest cas, NSwag amb destí C#. El resultat és una funció utilitzable per aquest client:
/// <summary>Create a banner</summary>
/// <param name="image">Adding new publication.</param>
/// <param name="name">Banner name</param>
/// <param name="isDisabled">Is disabled?</param>
/// <param name="analyticCode">Analytic Code</param>
/// <exception cref="SwaggerException">A server side error occurred.</exception>
public System.Threading.Tasks.Task Banners4Async(FileParameter image, string name, bool isDisabled, string analyticCode)
{
return Banners4Async(image, name, isDisabled, analyticCode, System.Threading.CancellationToken.None);
}
Quina seria la diferència de fer-lo per separat? En un altre post…