Search code examples
vue.jsquasar-framework

How to generate a Quasar SSR full-stack application?


Context: I am migrating from Nuxt to Quasar framework, and initially I want to port my full-stack apps from Nuxt to Quasar SSR mode. When using Nuxt, apps are full-stack by default, we just need to place backend related files in the server-directory (see https://nuxt.com/docs/guide/directory-structure/server#server-directory).

Question: I have already created my first Quasar application and added the SSR mode, but I could not find anything similar to Nuxt server-directory :-( So the question is: Does Quasar offer something similar to Nuxt server-directory? How to generate a Quasar SSR full-stack application?


Solution

  • I don't think Quasar offers something similar to server-directory out-of-the-box, but Quasar is a quite flexible framework, and we can easily achieve "SSR full-stack capabilities" by using a Quasar SSR-middleware :-)

    The idea is:

    SSR-middleware is an additional layer where all browser-requests are supposed to come through. Therefore, all we need to do is to add an additional SSR-middleware to intercept browser-requests when trying to hit a specific route (in this case, /api/*). Step-by-step:

    Disclaimer: it works only when running Quasar in SSR mode, of course.

    1. Considering that we have already created our Quasar application, and have already added the SSR mode (npm init quasar && cd my-app && quasar mode add ssr), let's add an additional SSR-middleware "api" with the command:
    quasar new ssrmiddleware api
    
    1. After that we need to include our SSR-middleware into quasar.config.js:
    ssr: {
        ...
        middlewares: [
            'api', // <= this is our SSR-middleware (keep this as first one)
            'render' // keep this as last one
        ]
    },
    ...
    
    1. And the last step is to implement the browser-requests interception into our SSR-middleware file src-ssr/middlewares/api.ts:
    export default ssrMiddleware(async ({ app, resolve }) => {
        app.all(resolve.urlPath('*'),(req, res, next) => {
            if (req.url.substring(0, 4) === '/api') {
                // Here we can implement our backend NodeJS/Express related operations.
                // See the example below "Real-life example", which provides
                // something similar to Next/Nuxt server-directory functionality.
                res.status(200).send(`Hi! req.method: ${req.method}, req.url: ${req.url}`);
            } else {
                next();
            }
        });
    });
    

    Now we can start our Quasar SSR application, and see our API in action by pointing the browser to http://localhost:9100/api/foo/bar/ Response => Hi! req.method: GET, req.url: /api/foo/bar/


    Real-life example:

    updated according to @David's suggestion. Thanks :-)

    1. In order to provide something similar to Next/Nuxt server-directory functionality, we need to include some additional lines of code into our SSR-middleware file src-ssr/middlewares/api.ts. The example below maps all available API-Handler (see apiHandlers object) and select them to handle browser requests according to the request URL:
    import { ssrMiddleware } from 'quasar/wrappers';
    
    const apiHandlers: { [key: string]: any } = {
      'req-info': import('../../src/api/req-info'),
      version: import('../../src/api/version'),
    };
    
    export default ssrMiddleware(async ({ app, resolve }) => {
      app.all(resolve.urlPath('*'), async(req, res, next) => {
        if (req.url.substring(0, 4) === '/api') {
          try {
            const path = req.url.split('?')[0].substring(5);
            const apiHandler = await apiHandlers[path];
            if (apiHandler) {
              await apiHandler.default(req, res);
            } else {
              res.sendStatus(404);
            }
          } catch (error) {
            console.error(error);
            res.sendStatus(500);
          }
        } else {
          next();
        }
      });
    });
    
    1. And after that we just need to create our NodeJS/Express API-Handlers in the folder /src/api/.
    • Example #1: /src/api/version.ts:
    import type { Request, Response } from 'express';
    export default function (req: Request, res: Response) {
      const version = require('../../package.json').version;
      res.status(200).send(version);
    }
    
    • Example #2: /src/api/req-info.ts:
    import type { Request, Response } from 'express';
    export default function (req: Request, res: Response) {
      res.setHeader('Content-Type', 'text/html');
      res.status(200).send(`
      <ul>
        <li>req.url: ${req.url}</li>
        <li>req.method: ${req.method}</li>
        <li>req.query: ${JSON.stringify(req.query)}</li>
      </ul>`);
    }
    

    I hope it helps, thanks! If so, please vote-up :-)

    StackBlitz project: https://stackblitz.com/edit/quasarframework-uocha6