Enroutament i React

Tenir l’aplicació ben enroutada no significa què després el enllaç amigable sigui funcional al 100%: si no es dins del context de la aplicació React. Per a aquesta raó s’haurà de dir al servidor HTTP que redireccioni qualsevol petició diferent a la ruta de index a la mateixa què el propi index que executa la aplicació.

En alguns casos com pot ser ASP MVC amb l’extensió de SPA, el propi host IIS o autohost de NET Core es configura per redireccionar aquestes peticions. En NodeJS passa exactament el mateix al desplegar-lo com una aplicació React Client.

En altres entorns no es ben bé així, com pot ser Apache o Azure Static Web Apps.

Apache:

Caldrà crear un fitxer .htaccess pròpi a la carpeta on es situï el build i dins caldrà especificar les següents línies

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /subdirectory
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule . /index.html [L]
</IfModule>

Azure Static Web Apps:

Caldrà crear un fitxer amb el nom staticwebapp.config.json i afegir les següents línies de configuració:

{
    "navigationFallback": {
        "rewrite": "index.html",
        "exclude": ["/static/*"]
    },
    "mimeTypes": {
        ".json": "text/json"
    }
}

Per a més informació:

Entitats amb fitxers: tot junt o per parts?

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…