It's 2025. Where is Wappler's localhost SSL Support?

One of the biggest social media platforms, Facebook, requires SSL for localhosts when using their Login API to develop applications. Many Wappler users who are developing mobile apps have also requested SSL support for localhost. Wappler leadership has commented in the past it was not necessary, even going so far as saying it just slows things down. Well, it's 2025, security awareness, practices, and policies are greater than ever. Interconnectivity, integration, and API platforms are everywhere. Secure connections even in development environments is an expectation. I think it's time Wappler provide some level of SSL support for localhosts now.

Today I installed mkcert and created certs for my localhost. Then went to the Wappler forums to search for how to install them when using Node. I only found other Wappler users asking the same questions for the past 5 years. No solutions unless the user wants to use ngrok or cloudflare, both use tunneling and additional configuration.

With my fresh localhost pem files created, it would be GREAT to know if/how to install them in my local Wappler environment. I also strongly urge the Wappler team to consider that development in today's environment has elevated security requirements due to the increased inter-connectivity we build into our applications. Not have SSL support, or even a manual process to provide a solution, is IMHO a big miss.

3 Likes

Hey @GraybeardCoder totally agree! Thanks for bringing it up. In my opinion requests like this should be given priority over "nice to have" but limited value items like AI.

Wappler is very much geared towards the novice to intermediate user. It seems that since the majority of the user base is made up of that group (no offence to anyone, totally fine), the Wappler team has made a decision to put the majority of their efforts into this group's requirements. Which is fine, that's their call to make, but it means you'll often run up again some big limitations of Wappler that you'll have to work out yourself and shoe horn in. At least that's been our experience.

Maybe you could add a feature request and see how it goes. Would be happy to vote for this.

2 Likes

Working with Cline in VSC, I now have a modified server.js file that might support https. I'm going to backup the unmodified server.js in my project and replace it with the following and restart Wappler...

if (process.env.NODE_ENV !== 'production') {
    require('dotenv').config();
}

process.on('uncaughtException', (e) => {
    // prevent errors from killing the server and just log them
    console.error(e);
});

const config = require('./setup/config');
const debug = require('debug')('server-connect:server');
const secure = require('./setup/secure');
const routes = require('./setup/routes');
const sockets = require('./setup/sockets');
const upload = require('./setup/upload');
const cron = require('./setup/cron');
const http = require('http');
const https = require('https'); // Added HTTPS module
const fs = require('fs'); // Added FS module
const express = require('express');
const endmw = require('express-end');
const cookieParser = require('cookie-parser');
const session = require('./setup/session'); //require('express-session')(Object.assign({ secret: config.secret }, config.session));
const cors = require('cors');
const app = express();

app.set('trust proxy', true);
app.set('view engine', 'ejs');
app.set('view options', { root: 'views', async: true });

app.disable('x-powered-by')

if (config.compression) {
    const compression = require('compression');
    app.use(compression());
}

if (config.abortOnDisconnect) {
    app.use((req, res, next) => {
        req.isDisconnected = false;
        req.on('close', () => {
            req.isDisconnected = true;
        });

        next();
    });
}

app.use(cors(config.cors));
app.use(express.static('public', config.static));
app.use(express.urlencoded({ extended: true }));
app.use(express.json({
    verify: (req, res, buf) => {
        req.rawBody = buf.toString()
    }
}));
app.use(cookieParser(config.secret));
app.use(session);
app.use(endmw);

upload(app);
secure(app);
routes(app);

// Define SSL options
const httpsOptions = {
  key: fs.readFileSync('/Users/d.../certs/localhost-key.pem'),
  cert: fs.readFileSync('/Users/d.../certs/localhost.pem')
};

const server = http.createServer(app); // Keep HTTP server
const httpsServer = https.createServer(httpsOptions, app); // Create HTTPS server
const io = sockets(httpsServer, session); // Attach sockets to HTTPS server (or decide if needed on both)

// Make sockets global available
global.io = io;

module.exports = {
    server, app, io,
    start: function(port) {
        // We add the 404 and 500 routes as last
        app.use((req, res) => {
            // if user has a custom 404 page, redirect to it
            if (req.accepts('html') && req.url != '/404' && app.get('has404')) {
                //res.redirect(303, '/404');
                req.url = '/404';
                app.handle(req, res);
            } else {
                res.status(404).json({
                    status: '404',
                    message: `${req.url} not found.`
                });
            }
        });
        
        app.use((err, req, res, next) => {
            debug(`Got error? %O`, err);
            // if user has a custom 500 page, redirect to it
            if (req.accepts('html') && req.url != '/500' && app.get('has500')) {
                //res.redirect(303, '/500');
                req.url = '/500';
                app.handle(req, res);
            } else {
                res.status(500).json({
                    status: '500',
                    code: config.debug ? err.code : undefined,
                    message: config.debug ? err.message || err : 'A server error occured, to see the error enable the DEBUG flag.',
                    stack: config.debug ? err.stack : undefined,
                });
            }
        });
        
        cron.start();

        // Start HTTP server
        server.listen(port || config.port, () => {
            console.log(`App listening at http://localhost:${config.port}`);
        });

        // Start HTTPS server on port 3443
        const httpsPort = 3443;
        httpsServer.listen(httpsPort, () => {
            console.log(`App listening securely at https://localhost:${httpsPort}`);
        });
    }
};

1 Like

Got SSL working locally. Yesterday I installed mkcert on my mac and created the certificate files into a folder called certs/

  1. The path to the certs in server.js needs to be relative to the lib/ folder. I created a folder certs/ and placed it under .wappler/lib/ so now the lines in server.js look like this:
// Define SSL options
const httpsOptions = {
  key: fs.readFileSync('./lib/certs/localhost-key.pem'),
  cert: fs.readFileSync('./lib/certs/localhost.pem')
};
  1. The docker-compose.yml file in the Development target directory needed to be updated to look like this:
    ports:
      - '80:3000'
      - '3443:3443'
  1. I had to delete the existing compose stack. I first stopped all services using Wappler with my development target selected. Then, using Docker Desktop, I stopped the container for my project, then deleted this container. It's the compose stack.
  2. Then I used Wappler start all services, knowing it would rebuild the compose stack using all Wappler configs "and stuff". No errors on start up!

At this point, I confirmed the ports were in use by refreshing the container view in Docker Desktop. You could also run docker ps in the terminal and look at the ports column. Then I tried https://localhost:3443 in my browser and it connected! My connection is secure and my cert is valid.

There is an issue I'm seeing now after logging into my site. A server connect is failing. I'll look into that next.

The server connect issue had nothing to do with running SSL on my localhost. A query of my customer table said a column was missing.

The issue was that I had a db save file under .wappler/targets/Development/db_init/ and when I used Wappler to rebuild the container, it used that save file to rebuild the DB. So if you're going to follow along, please check this folder for a save file and make sure it's current. I do not know what will happen if there's no save file at all as that wasn't my situation. Good news for me is that I only had to manually restore one column in 1 table which was an easy alter table statement within mysql.

I'm doing a full app test next to see if anything else pops up.

1 Like

Been a few days of development using https on localhost and no issues. I've been able to deploy to staging and production without issue as well. There is a concern however...while Wappler uses separate deployment target docker compose yaml files, it uses a single server.js file for all targets. I can see my modified server.js file now on my production and staging servers. If the containers were to be deleted there, such as I did with my development target, the new server.js settings would be implemented, causing errors because the ports would be wrong (I'm using Traefik for HTTPS), and the certificate paths (on my local machine) would not be found. Therefore, what I've done has allowed me to continue working on an integration between my site and Facebook on my local machine, but it will cause an outage if the other targets' containers are rebuilt. I do have a backup of my original server.js file if needed.

In Wappler 7.3 you can use SSL locally: