AWS Lambda with Knex JS and RDS Postgres - postgresql

I have been doing some research and I'm not able to find a good answer about using Knex JS within a Lambda function:
How do I use Knex with AWS Lambda? #1875
Serverless URL Shortener with Apex and AWS Lambda
Use Promise.all() in AWS lambda
Here is what I have in my index.js:
const knex = require('knex')({
client: 'pg',
connection: {...},
});
exports.handler = (event, context, callback) => {
console.log('event received: ', event);
console.log('knex connection: ', knex);
knex('goals')
.then((goals) => {
console.log('received goals: ', goals);
knex.client.destroy();
return callback(null, goals);
})
.catch((err) => {
console.log('error occurred: ', err);
knex.client.destroy();
return callback(err);
});
};
I am able to connect and execute my code fine locally, but I'm running into an interesting error when it's deployed to AWS - the first call is always successful, but anything after fails. I think this is related to the knex client being destroyed, but then trying to be used again on the next call. If I re-upload my index.js, it goes back to working for one call, and then failing.
I believe this can be resolved somehow using promises but this my first time working with Lambda so I'm not familiar with how it's managing the connection to RDS on subsequent calls. Thanks in advance for any suggestions!

For me, it worked on my local machine but not after deploying. I was kind of be mislead.
It turns out the RDS inbound source is not open to my Lambda function. Found solution at AWS Lambda can't connect to RDS instance, but I can locally?: either changing RDS inbound source to 0.0.0.0/0 or use VPC.
After updating RDS inbound source, I can use Lambda with Knex successfully.
The Lambda runtime I am using is Node.js 8.10 with packages:
knex: 0.17.0
pg: 7.11.0
The code below using async also just works for me
const Knex = require('knex');
const pg = Knex({ ... });
module.exports.submitForm = async (event) => {
const {
fields,
} = event['body-json'] || {};
return pg('surveys')
.insert(fields)
.then(() => {
return {
status: 200
};
})
.catch(err => {
return {
status: 500
};
});
};
Hopefully it will help people who might meet same issue in future.

The most reliable way of handling database connections in AWS Lambda is to connect and disconnect from the database within the invocation process itself.
In your code above, since you disconnected already after the first invocation, the second one does not have a connection anymore.
To fix it, just move your instantiation of knex.
exports.handler = (event, context, callback) => {
console.log('event received: ', event);
// Connect
const knex = require('knex')({
client: 'pg',
connection: {...},
});
console.log('knex connection: ', knex);
knex('goals')
.then((goals) => {
console.log('received goals: ', goals);
knex.client.destroy();
return callback(null, goals);
})
.catch((err) => {
console.log('error occurred: ', err);
// Disconnect
knex.client.destroy();
return callback(err);
});
};
There are ways to reuse an existing connection but success rates for that varies widely depending on database server configuration and production load.

I got the exact same issue as you said: Used destroy() in an AWS lambda function (like this: await knex.destroy() at the bottom) and suddenly all my AWS lambdas were in error.
Because I did not suspect it, I searched for hours what was causing the issue and even started to investigate using lambda + vpc + nat etc.. Turns out it's just that AWS freezes lambda in a way that if you destroy the connection, on the next handler invocation it will try to reuse the connection.
Solution: do not use .destroy() at the end of lambda and redeploy.

Related

Can Bunjs be used as a backend server?

Now we can start a react App with bun as a server
Can we use Bunjs as complete backend server?
For Example, Can bun run this code?
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.send('hello world')
})
app.listen(3000)
I guess Bun does not YET implement all node.js api's. I tried http and it seems currently missing. And as much I understand it currently has its own built-in HTTP server.
Check the "Getting started" section on -> https://bun.sh/
A sample server:
export default {
port: 3000,
fetch(request) {
return new Response("Welcome to Bun!");
},
};
(This example reminds me of serverless functions.)
As this is the case, it seems you can not rely on Node.js http, or most probably any server framework like express.
At least for now, bun's roadmap (https://github.com/oven-sh/bun/issues/159) shows a line, which I am not sure is talking about node's http server or sth. else about Bun's own server.
Once complete, the next step is integration with the HTTP server and
other Bun APIs
Bun api is really different from nodejs, I created a library called bunrest, a express like api, so new user does not need to learn much about bun.
Here is how to use it
Install the package from npm
npm i bunrest
To create a server
const App = require('bunrest');
const server = new App.BunServer();
After that, you can call it like on express
server.get('/test', (req, res) => {
res.status(200).json({ message: 'succeed' });
});
server.put('/test', (req, res) => {
res.status(200).json({ message: 'succeed' });
});
server.post('/test', (req, res) => {
res.status(200).json({ message: 'succeed' });
});
To start the server
server.listen(3000, () => {
console.log('App is listening on port 3000');
});
Other way is using Hono: https://honojs.dev/
There is a working demo: https://github.com/cachac/bun-api
Import package and set new instance
import { Hono } from 'hono'
const app = new Hono()
Create routes:
Instead of NodeJs res.send(), use c.json({})
app.get('/hello', c => c.json({ message: 'Hello World' }))
export and run api server:
export default {
fetch: app.fetch,
port: 3000
}

How to enforce SLONIK to close open pools

THE PROBLEM
I have a NodeJs server where I'm using jest for testing. In case of integration tests i get a message after all tests where passing:
"Jest has detected the following X (15-20) open handles potentially keeping Jest from exiting"
I knew what this means, have already seen it when i was using Sequelize as ORM, but now I'm using Slonik.
I found this topic what was really useful:
https://github.com/gajus/slonik/issues/63
so when i set the idleTimeOut as advised it is solved.
test("foo", async () => {
const slonik = createPool(
`postgres://postgres:password#127.0.0.1:7002/postgres`,
{
maximumPoolSize: 1,
minimumPoolSize: 1,
idleTimeout: 1 // milliseconds!
}
);
await slonik.many(
sql`SELECT table_name FROM information_schema.tables WHERE table_schema='public';`
);
});
I tried to solve this problem from an other perspective. My ide was to close the connection in the afterAll block of jest. The test setup is:
const db = container.resolve(DbConnectionPool).connection;
let appServer;
let api;
beforeEach(() => {
appServer = app.listen(config.port);
api = supertest(appServer);
});
afterEach(async () => {
appServer.close();
await db.query(sql`TRUNCATE reports`);
});
What i have tried in the afterAll block:
afterAll(async () => {
await db.end()
});
It does not solves my problem as the documentation tells:
'Note: pool.end() does not terminate active connections/ transactions.'
I do not found anything about how to enforce the close of a pool until now.
So thought i can be tricky and i will close the connection using SQL:
afterAll(async () => {
await db.query(sql`DISCONNECT ALL`);
});
Does not work as well.
I still had an idea to play with. Slonik documentation tells, that the default idleTimeOut for a connection is 5000ms in default.
So i have tried to set a timeout in the afterAll block with 6000 ms, but i still get the warning from Jest.
So does anyone have any idea how to force-close the connection for my tests?

Connected to MLab, but won't connect to localhost

I had this working fine on both localhost and MLab, but then had to switch databases. After much trying I got the database up on MLab, but now it's not connecting to my localhost. Here is my server.js file:
const path = require("path");
const PORT = process.env.PORT || 3001;
const app = express();
const mongoose = require("mongoose");
const routes = require("./routes");
// Connect to the Mongo DB
mongoose.connect(process.env.MONGODB_URI || 'mongodb://XXUSERXX:XXPASSWORDXX#ds217388-a0.mlab.com:17388,ds217388-a1.mlab.com:17388/<dbname>?replicaSet=rs-ds217388', { useNewUrlParser: true });
mongoose.connection.on("open", function (ref) {
console.log("Connected to mongo server.");
});
mongoose.connection.on('error', function (err) { console.log(err) });
// Define middleware here
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// Serve up static assets (usually on heroku)
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
}
// Add routes, both API and view
app.use(routes);
// Define API routes here
// Send every other request to the React app
// Define any API routes before this runs
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "./client/build/index.html"));
});
app.listen(PORT, () => {
console.log(`🌎 ==> API server now on port ${PORT}!`);
});
The only line of code I changed was this one below, this is what it was previously:
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/wineDB', { useNewUrlParser: true });
I have this app connected in Heroku and had MONGODB_URI defined in the Config Vars, but it wasn't working with the second database until I manually put the connection string in my server.js file. It worked fine with the first one, I don't understand why!
How do I get it to connect to find localhost when it's not running off of MLAB so I can test? Thanks for the help.
It looks like the confusion is from a few different combinations of whether the environment variable being defined or not, as well as whether or not your app is using the variable, instead of falling back to what is defined.
The MONGODB_URI environment variable should contain the connection string for your mLab database and be defined in your Heroku environment both locally and when deployed. I'm assuming that the variable process.env.LOCAL will only be present on your local environment, in situations where your app should be connecting to the local database.
In these cases, something like the following should work:
if(process.env.LOCAL || process.env.MONGODB_URI) {
mongoose.connect(process.env.LOCAL || process.env.MONGODB_URI, { useNewUrlParser: true });
...
} else {
console.log("MongoDB connection string not defined!");
}
We place process.env.LOCAL first, followed the by ||, to say that it gets preference when connecting. Mongoose should then connect to whatever is defined in process.env.LOCAL if present (i.e. your local MongoDB database), falling back to process.env.MONGODB_URI (i.e. mLab) otherwise.
Lastly, it's wrapped in a simple if-else to print out an error message if both values are not defined.

AWS Lambda timing out on MongoDB Atlas connection

I'm just trying to write a simple Lambda function to insert data into my MongoDB Atlas cluster. I've set the cluster to accept all incoming traffic (0.0.0.0/0) and confirmed that I can connect locally.
For AWS Lambda, I set up a VPC using the VPC wizard, and I gave my Lambda function a security role with full admin access. I set the timeout to 12 seconds, but I'm still getting the following error:
Response:
{
"errorMessage": "2018-11-19T15:17:23.200Z 3048e1fd-ec0e-11e8-a03d-fb79584484c5 Task timed out after 11.01 seconds"
}
Request ID:
"3048e1fd-ec0e-11e8-a03d-fb79584484c5"
Function Logs:
START RequestId: 3048e1fd-ec0e-11e8-a03d-fb79584484c5 Version: $LATEST
2018-11-19T15:17:12.191Z 3048e1fd-ec0e-11e8-a03d-fb79584484c5 Calling MongoDB Atlas from AWS Lambda with event: {"address":{"street":"2 Avenue","zipcode":"10075","building":"1480","coord":[-73.9557413,40.7720266]},"borough":"Manhattan","cuisine":"Italian","grades":[{"date":"2014-10-01T00:00:00Z","grade":"A","score":11},{"date":"2014-01-16T00:00:00Z","grade":"B","score":17}],"name":"Vella","restaurant_id":"41704620"}
2018-11-19T15:17:12.208Z 3048e1fd-ec0e-11e8-a03d-fb79584484c5 => connecting to database
2018-11-19T15:17:12.248Z 3048e1fd-ec0e-11e8-a03d-fb79584484c5 (node:1) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
END RequestId: 3048e1fd-ec0e-11e8-a03d-fb79584484c5
REPORT RequestId: 3048e1fd-ec0e-11e8-a03d-fb79584484c5 Duration: 11011.08 ms Billed Duration: 11000 ms Memory Size: 128 MB Max Memory Used: 29 MB
2018-11-19T15:17:23.200Z 3048e1fd-ec0e-11e8-a03d-fb79584484c5 Task timed out after 11.01 seconds
The relevant part of my code for connecting is (with user and pass being the appropriate values):
const MongoClient = require('mongodb').MongoClient;
let atlas_connection_uri = "mongodb+srv://<user>:<pass>#restaurantcluster-2ylyf.gcp.mongodb.net/testdb"
let cachedDb = null;
exports.handler = (event, context, callback) => {
var uri = atlas_connection_uri
if (atlas_connection_uri != null) {
processEvent(event, context, callback);
}
else {
atlas_connection_uri = uri;
console.log('the Atlas connection string is ' + atlas_connection_uri);
processEvent(event, context, callback);
}
};
function processEvent(event, context, callback) {
console.log('Calling MongoDB Atlas from AWS Lambda with event: ' + JSON.stringify(event));
var jsonContents = JSON.parse(JSON.stringify(event));
//date conversion for grades array
if(jsonContents.grades != null) {
for(var i = 0, len=jsonContents.grades.length; i < len; i++) {
jsonContents.grades[i].date = new Date();
}
}
context.callbackWaitsForEmptyEventLoop = false;
try {
if (cachedDb == null) {
console.log('=> connecting to database');
MongoClient.connect(atlas_connection_uri, function (err, client) {
cachedDb = client.db('testdb');
return createDoc(cachedDb, jsonContents, callback);
});
}
else {
createDoc(cachedDb, jsonContents, callback);
}
}
catch (err) {
console.error('an error occurred', err);
}
}
I suspect that something is going on with my VPC firewall/permissions/security group considering the fact that I can connect from my local machine, but I have no idea how that could be the case when I'm granting full admins privileges in my security role and I've set all outgoing VPC traffic to my public subnet.
I would appreciate any advice/help in solving this!
edit to provide more info:
The function console.logs '=> connecting to database' and then immediately times out at MongoClient.connect (confirmed by attempting to console.log directly after that).

Mongo connection occasionally makes the lambda function timeout

I have been using MLab MongoDB and mongoose library to create a db connection inside a serverless (Lambda) handler. It works smoothly on local machine. But sometimes it doesn't work after deployment.The request returns an Internal server error. The weird thing is sometimes it works. But If I remove the database connection code, the handler works. The serverless log just says Process exited before completing request. No real errors so no idea what to do.
The db connection looks like this:
handler.js
// Connect to database
mongoose.connect(process.env.DATABASE_URL, {
useMongoClient: false
}).then((ee) => {
console.log('------------------------invoke db ', ee);
})
.catch(err => console.error('-----------error db ', err));
No error in here too. Any idea what's happening?
When you get Process exited before completing request, it means that the node process has crashed before Lambda was able to call callback. If you go to Cloudwatch logs, there would be an error and stack trace of what happened.
You should connect to the MongoDB instance inside your handler and before you call callback(), disconnect first.
It would be like this...
exports.handler = (event, context, callback) => {
let response;
return mongoose.connect(process.env.DATABASE_URL, {
useMongoClient: false
}).then((ee) => {
// prepare your response
response = { hello: 'world' }
}).then(() => {
mongoose.disconnect()
}).then(() => {
// Success
callback(null, response)
}).catch((err) => {
console.error(err);
callback(err);
})
};
Here is an article explaining with details how lambda work with node and an example of how to implement DB connection.
Differently of #dashmug suggested, you should NOT disconnect your DB since connecting every time will decrease your performance.