Lua Script in Redis using node_redis
Tags: node, javascript, redis, lua
Published: 2022-06-20 05:00:17

---
title: Lua Script in Redis using node_redis
published: true
date: 2022-06-19 18:56:17 UTC
tags: #nodejs #javascript #redis #lua
canonical_url: 
---

Redis allows us to upload and execute Lua scripts on the server. As these scripts are executed on the server, it makes the reading and writing data very efficient. Lua scripts behave like stored procedures for Redis. where it let us run our application logic inside Redis.

Redis allows Lua scripts to have Blocking semantics which means when it is invoked blocks the execution of its invoker, Hence making the execution of Lua scripts atomic in nature.

Lua scripts are considered a part of the client-side application. It executes on the server but they are not named, versioned, or persisted. As a result, these scripts can be missing from the Redis server, and it is up to the application code to reload these scripts.

## Lua Programming Language

Lua is a powerful and fast programming language that is easy to use and embed into your application. It's lightweight and portable with only 21 keywords.

![Screenshot from 2022-06-12 02-23-37.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1654980878562/YLBuuObst.png)

Lua provides a single data structure which is tables. All data structures like arrays, records, lists, queues, and sets are represented with tables in Lua.

```
backend_frameworks = {"django","express","flask"}

```

## Installing Lua Programming

- For installing Lua in ubuntu

- For installing Lua in MacOS

**Example of Lua programming language**

```
function sum(a, b)
    return a + b
end

local a = 1
local b = 2
local c = sum(a, b)
print(c)

```

## Lua script and Redis

Redis provides **EVAL** Command, which allows execution on Lua scripts.

**Examples**

```
EVAL "return redis.call('get', KEYS[1])" 1 key

```

![Screenshot 2022-06-17 at 2.58.42 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655415019049/LnVh_roGR.png)

In this example, We can see the **EVAL** command is used to execute **Lua script**. It takes several arguments a script, numkey, keys, and name. **numkey** is the number of keys and names to be taken. These keys and names are additional inputs and are not necessarily required. these keys and names provided in arguments are available to the script through **KEYS** and **ARGV** global runtime variables.

```
EVAL "return { KEYS[1], KEYS[2], ARGV[1], ARGV[2], ARGV[3] }" 2 key1 key2 arg1 arg2 arg3

```

![Screenshot 2022-06-17 at 4.46.48 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655421459686/xSG8erZ7T.png)

## Interacting with Redis Commands from a Lua script

Lua script uses **redis.call()** or **redis.pcall()** function to interact with redis.

**Example of SET command**

```
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 key1 val1

```

![Screenshot 2022-06-17 at 4.58.38 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655422173335/qWk1OU_sI.png)

**redis.call()** function take **SET** command, and executes it with given key and name arguments.

## Loading and executing Lua script

Redis provides a **SCRIPT LOAD** command for caching Lua script. It returns a **sha1 hash** , which can be used to execute the stored scripts. **SCRIPT LOAD** command doesn't validate the script. For executing cached script Redis provides the **EVALSHA** command.

```
SCRIPT LOAD "return { KEYS[1] , ARGV[1] }"


SCRIPT LOAD "returns { KEYS[1] , }"

```

Now, both scripts are cached inside Redis and return a sha1 hash.

![Screenshot 2022-06-17 at 5.48.39 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655425156361/MebNvjqgt.png)

```
EVALSHA 9e9ef47970b6b31a079e3f4d4bd2e755c53ba3e0 1 key1 val1

```

![Screenshot 2022-06-17 at 6.00.03 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655425862107/_f_NOgo-v.png)

scripts stored in Redis are not persisted but removed explicitly. We can use the **SCRIPT FLUSH** command in Redis to allow the flushing of all stored scripts. it has two modes which are Async and Sync.

- ASYNC: flushes the cache asynchronously
- SYNC: flushes the cache synchronously

```
SCRIPT FLUSH ASYNC

```

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655427279687/Uk-GAoXDC.png)

## Running Lua Script File Using **redis-cli**

Most of the time our Lua script will be in the **.lua** file and we will need to store and execute that file directly using redis-cli.

**Syntax:**

```
redis-cli --eval fileName.lua keys , argv

```

**Example:**

This Lua script is for setting key-value pairs in a set if it doesn't exist. For this, we use the GET command for getting the value if we didn't get the value we will use the SET command to put this key value in the Redis set.

```
local key = KEYS[1];
local value = ARGV[1];

local oldValue = redis.call('GET', key);

if (oldValue == false) then
  redis.call('SET', key, value);
  return {key, value};
else
  return {key, oldValue};
end

```

**Executing this script**

```
redis-cli --eval ./addKeyValue.lua setKey1 , value2

```

![Screenshot 2022-06-17 at 11.38.27 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655446163770/J11RJOjJP.png)

**Debugging this script**

Redis also provides a Lua debugger. To use it, pass the --ldb flag.

```
redis-cli --ldb --eval ./addKeyValue.lua setKey1 , value2

```

![Screenshot 2022-06-17 at 11.43.14 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655446447696/kA8OoDWDb.png)

**Caching this script**

We can simply use the **SCRIPT LOAD** command to store scripts in Redis

```
redis-cli SCRIPT LOAD "$(cat ./addKeyValue.lua)"

```

![Screenshot 2022-06-17 at 11.47.58 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655446707939/RQftbSxix.png)

**Executing this Stored script**

```
redis-cli EVALSHA "5828794b7233cb735e77a5346aab6384befa6179" 1 "key1" "val1"

```

![Screenshot 2022-06-17 at 11.53.44 AM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655447050831/OdORPfXoC.png)

## Managing Lua Scripts for Redis in Node

There can be many ways to use Lua scripts with our nodeJS application.

Few of which are :

- Using node\_redis " **scriptLoad**" and " **evalSha**" Commands 
- Using " **eval**" Command

Before exploring these methods for executing Lua scripts from our nodeJS application. First, let's create the folder structure. I mostly keep all my Lua scripts inside a folder called **luaScripts** inside my project directory.

![Screenshot from 2022-06-18 21-08-09.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655566716589/ASJ49-3a0.png)

Lua scripts - **addKeyIfNotExists.lua** , we are using

```
local key = tostring(KEYS[1]);
local value = tostring(ARGV[1]);

local oldValue = redis.call('GET', key);

if (oldValue == false) then
  redis.call('SET', key, value);
  return {key, value};
else
  return {key, oldValue};
end

```

Let's create an HTTP webserver with a [Redis connection](https://dev.to/itsvinayak/redis-clients-and-connecting-to-redis-516o)

```
var http = require('http');
var fs = require('fs');
var redis = require('redis');
var client = redis.createClient();

client.connect();
client.on('ready', () => console.log('Redis is ready'));
client.on('error', (err) => console.log('Error ' + err));

var PORT = process.env.PORT || 3000;

var server = http.createServer(function (req, res) {
  res.writeHead(200, { 'Content-Type': 'text/html' });
  client.GET('key').then((reply) => {
    res.write(` hello ${reply}`);
    res.end();
  }).catch((err) => {
    console.log(err);
    res.write(err);
    res.end();
  });
});

server.listen(PORT, function () {
  console.log(`Server listening on port ${PORT}`);
});

```

![Screenshot from 2022-06-19 20-55-59.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655652412649/tp64kYwQV.png)

#### Using node\_redis " **scriptLoad**" and " **evalSha**" Commands

Creating a Lua variable of type dictionary to store Script and Script Hash.

```
var lua = {
  script: fs.readFileSync('./luaScripts/addKeyIfNotExists.lua', 'utf8'),
  sha: null
};

```

Modifying our nodeJS script to catch Lua script into Redis DB

```
server.listen(PORT, function () {
  client.scriptLoad(lua.script).then(sha => {
    lua.sha = sha;
    console.log(`Script loaded ${sha}`);
    console.log(`Server listening on port ${PORT}`);
  }).catch(err => {
    console.log(err);
  });
});

```

while starting the web server, we will use the **scriptLoad** command of node\_redis, to load our Lua script and store the hash in **lua.sha** variable.

For using this cached script, we will use the **evalSha** Redis command.

```
client.evalSha(lua.sha, {
      keys: ['key1'],
      args: ['value1']
    }).then((reply) => {
      res.write(` key : ${reply[0]} value: ${reply[1]}`);
      res.end();
    }).catch((err) => {
      console.log(err);
      res.write(err);
      res.end();
    });

```

Complete nodeJS Scripts

```
var http = require('http');
var fs = require('fs');
var redis = require('redis');
var client = redis.createClient();

client.connect();
client.on('ready', () => console.log('Redis is ready'));
client.on('error', (err) => console.log('Error ' + err));

var PORT = process.env.PORT || 3000;

var lua = {
  script: fs.readFileSync('./luaScripts/addKeyIfNotExists.lua', 'utf8'),
  sha: null
};

var server = http.createServer(function (req, res) {
  res.writeHead(200, { 'Content-Type': 'text/html' });
  if (req.url === '/add') {
    client.evalSha(lua.sha, {
      keys: ['key1'],
      args: ['value1']
    }).then((reply) => {
      res.write(` key : ${reply[0]} value: ${reply[1]}`);
      res.end();
    }).catch((err) => {
      console.log(err);
      res.write(err);
      res.end();
    });
  } else {
    res.write('<h1>Hello World</h1>');
    res.end();
  }
});

server.listen(PORT, function () {
  client.scriptLoad(lua.script).then(sha => {
    lua.sha = sha;
    console.log(`Script loaded ${sha}`);
    console.log(`Server listening on port ${PORT}`);
  }).catch(err => {
    console.log(err);
  });
});

```

![Screenshot from 2022-06-19 21-20-15.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1655653898249/vcJ-xH73M.png)

#### Using "eval" Command

For testing and debugging, we can use eval Command. This command takes three parameters Lua Script, keys and args.

```
  client.eval(lua.script, {
      keys: ['key1'],
      args: ['value1']
    }).then((reply) => {
      res.write(` key : ${reply[0]} value: ${reply[1]}`);
      res.end();
    }).catch((err) => {
      console.log(err);
      res.write(err);
      res.end();
    });

```

Complete nodeJS Scripts

```
var http = require('http');
var fs = require('fs');
var redis = require('redis');
var client = redis.createClient();

client.connect();
client.on('ready', () => console.log('Redis is ready'));
client.on('error', (err) => console.log('Error ' + err));

var PORT = process.env.PORT || 3000;

var lua = {
  script: fs.readFileSync('./luaScripts/addKeyIfNotExists.lua', 'utf8'),
  sha: null
};

var server = http.createServer(function (req, res) {
  res.writeHead(200, { 'Content-Type': 'text/html' });
  if (req.url === '/add') {
    client.eval(lua.script, {
      keys: ['key1'],
      args: ['value1']
    }).then((reply) => {
      res.write(` key : ${reply[0]} value: ${reply[1]}`);
      res.end();
    }).catch((err) => {
      console.log(err);
      res.write(err);
      res.end();
    });
  } else {
    res.write('<h1>Hello World</h1>');
    res.end();
  }
});

server.listen(PORT, function () {
  console.log(`Server listening on port ${PORT}`);
});

```