Express.js with mssql efficient pool management

Viewed 215

I am new to javascript world and my first project was to create an efficient MSSQL rest API using Express. I started by studying a few basic examples and I ended up with this which is working fine:

const utils = require('../utils');
const config = require('../../config');
const sql = require('mssql');

const getEvents = async () => {
    try {
        let pool = await sql.connect(config.sql);
        const sqlQueries = await utils.loadSqlQueries('events');
        const eventsList = await pool.request().query(sqlQueries.eventslist);
        console.log(eventsList);
        return eventsList.recordset;
        
    } catch (error) {
        console.log(error.message);
    }
}
module.exports {getEvents};

I am using a separate js file to handle my routes, a config file to handle my .env variables an eventController.js to handle the events (like the get events) and also a utils file to handle my sql queries.

index.js

const express = require('express');
const config = require('./config');
const cors = require('cors');
const bodyParser = require('body-parser');
const eventRoutes = require('./routes/eventRoutes');

const app = express();


app.use(express.json());
app.use(cors());
app.use(bodyParser.json());
app.use('/api', eventRoutes.routes);

app.listen(config.port, () => {
    console.log('Server is listening on ' + config.url)
}).on('error', (e) => {
    console.log('Error starting the server', e.message)
});

config.js

const dotenv = require('dotenv');
const assert = require('assert');

dotenv.config();

const { PORT, HOST, HOST_URL, SQL_USER, SQL_PASSWORD, SQL_SERVER, SQL_DATABASE } = process.env;
const sqlEncrypt = process.env.ENCRYPT === true;

assert(PORT, 'PORT is required');
assert(HOST, 'HOST is requred');

module.exports = {
    port: PORT,
    host: HOST,
    url: HOST_URL,
    sql: {
        server: SQL_SERVER,
        database: SQL_DATABASE,
        user: SQL_USER,
        password: SQL_PASSWORD,
        options: {
            encrypt: sqlEncrypt,
            enableArithAbort: true
        },
    },
};

routes.js

const express = require('express');
const eventControler = require('../controllers/eventController');
const router = express.Router();

const { getEvents, getEvent, addEvent, updateEvent, deleteEvent } = eventControler; 

router.get('/events', getEvents); 
router.get('/event/:id', getEvent);
router.post('/event', addEvent); 
router.put('event/:id', updateEvent);
router.delete('/event/:id', deleteEvent);

module.exports = {
    routes: router
}

eventsController.js

const eventData = require('../data/events');

const getAllEvents = async (req, res, next) => {
    try {

        const eventlist = await eventData.getEvents();
        res.send(eventlist);        
    } catch (error) {
        res.status(400).send(error.message);
    }
}
module.exports {getEvents};

I am also using a utils.js file to convert my.sql files to sql queries nothing too fancy there I am sure most of you are aware of this function as its widely used. Now, although the API works as intended I wanted to fine-tune connection pool management a bit to avoid the occasion of the server making new pools for each user request and also for debugging and state monitoring reasons, so having studied a lot of issues here I decided to introduce a new file called client.js:

const sql = require("mssql");
const config = require("../config");

const client = new sql.ConnectionPool(config.sql)
    .connect()
    .then(pool => {
        console.log('connected to MSSQL server')
        return pool
    })
    .catch(err => console.log('Database connection failed! Bad config: ', err))


module.exports = {
    sql,
    client
};

and then I modified my getEvents function like this:

const getEvents = async () => {
    try {
        const pool = await client;
        const sqlQueries = await utils.loadSqlQueries('events');
        const list = await pool.request().query(sqlQueries.eventsList);
        console.log(list);
        return list.recordset;
        

    } catch (error) {
        return error.message;
    }
}

I am getting a 200 response but the body is empty and the recordset is undefined. I also tried the approach described here: pool.request is not a function

modifying my client.js to:

const sql = require("mssql");
const config = require("../config");

const client = new sql.ConnectionPool(config.sql);
const poolConnection = client.connect()
    .then(pool => {
        console.log('connected to MSSQL server')
        return pool
    })
    .catch(err => console.log('Database connection failed! Bad config: ', err))


module.exports = {
    
    poolConnection,
    client
};

and I also modified my getEvents.js to:

const config = require('../../config');
const sql = require('mssql');
const {poolConnection, client} = require('./client');

const getEvents = async () => {
await poolConnection;
    try {
        
        const sqlQueries = await utils.loadSqlQueries('events');
        const eventsList = await client.request().query(sqlQueries.eventslist);
        console.log(eventsList);
        return eventsList.recordset;

    } catch (error) {
        console.log(error.message);
    }
}
module.exports {getEvents};

But I didn't get any luck with either. The response is 200 still but the recordset is undefined. What am I missing here?

Could you please point me to the right direction as to how I can use the global pool efficiently and not flood the sql server with new connection requests ?

1 Answers

Just from a cursory glance, this appears incorrect:

const list = await pool.request().query(sqlQueries.eventsList);

The await will apply only to pool.request().

It should be:

const request = await pool.request();
const list = await request.query(sqlQueries.eventsList);

That's not how I would structure my production code but I believe it's the answer to this problem with your code as written.

Related