Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates Node Server for latest Node, Modules and Node-Redis v4 Client #99

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion docs/010-application-development.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ You can find the same REST Service that uses RediSearch developed with different

* [RediSearch & Java : Jedis/JRediSearch](../../../tree/master/sample-app/redisearch-jedis-rest)

* [Node.js : Node RediSearch](../../../tree/master/sample-app/redisearch-node-rest)
* [Node.js : Node-Redis](../../../tree/master/sample-app/redisearch-node-rest)

* [Python : Python RediSearch](../../../tree/master/sample-app/redisearch-python-rest)

2 changes: 1 addition & 1 deletion sample-app/redisearch-node-rest/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:10
FROM node:16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to 18 (current LTS)?


WORKDIR /usr/src/app

430 changes: 163 additions & 267 deletions sample-app/redisearch-node-rest/NodeSearchService.js

Large diffs are not rendered by default.

103 changes: 32 additions & 71 deletions sample-app/redisearch-node-rest/README.md
Original file line number Diff line number Diff line change
@@ -1,101 +1,62 @@
# RediSearch: Node.js Sample
# RediSearch: Node.js Sample Application

This application requires [Node.js](https://nodejs.org/) 14.8.0 or higher to run.

## Application Archictecture

The REST API routes and Express server are defined and configured in `server.js`. Each route makes calls to an instance of a search service that executes Redis/RediSearch commands and formats the responses into JSON objects. The code for this is contained in `NodeSearchService.js`.

## Coding the application
## Running the application in Docker

#### 1- Create the Project
You can build and run the application with Docker using the following commands.

Follow the `npm` steps
### Build

```bash
$ docker build -t redis/search-backend-node .
```
$ npm init
```

#### 2- Add Dependencies

Add the dependencies:

* [Express](https://www.npmjs.com/package/express)
* [Node RediSearch](https://www.npmjs.com/package/redis-redisearch)

```
$ npm install express redis redis-redisearch --save
```


#### 3- Create REST API Routes
This command creates a new image and builds the Node.js project into it.

Create the `server.js` file and add the following code

```js
const express = require('express')
const app = express()
const port = 3003


app.get('/api/1.0/', (req, res) => {
res.json({"status" : "started"});
})


app.get('/', (req, res) => {
res.send('RediSearch Node REST Server Started')
})

app.listen(port, () => {
console.log(`RediSearch Node listening at http://localhost:${port}`)
})
### Run

```bash
$ docker run --rm \
--env "REDIS_URL=redis://host.docker.internal:6379" \
--env "REDIS_INDEX=idx:movie" \
--name "redisearch-backend-node"\
-p 8086:8086 redis/search-backend-node
```

This will be the base of the various API endpoints.


#### 4- Create a NodeSearchService

In this sample application all the RediSearch interactions will be done in `NodeSearchService.js` file.


### Running the application in Docker

You can run build and run the application from docker using the following commands:
This command runs the container and starts the application, which will listen on port 8086.

**Build**
## Running the Application Locally

```shell script

> docker build -t redis/search-backend-node .
This application is built with [Express](https://www.npmjs.com/package/express) and [Node Redis](https://www.npmjs.com/package/redis). Install these dependencies as follows:

```bash
$ npm install
```

This command will create a new image and build the Node.js project into it.

**Run**
Then start the application:

```shell script
> docker run --rm \
--env "REDIS_URL=redis://host.docker.internal:6379" \
--env "REDIS_INDEX=idx:movie" \
--name "redisearch-backend-node"\
-p 8086:8086 redis/search-backend-node
```bash
$ npm start
```

### Running the application locally

To run the application on your local machine:
Use this command to run the application using [nodemon](https://www.npmjs.com/package/nodemon) so that the server is automatically restarted for you if you make code changes:

```shell script
> npm install
> npm start
```bash
$ npm run dev
```

### Accessing the API

You can now access the REST Search service using the following URL:
You can now access the REST Search service using the following example URLs:

* http://localhost:8086/api/1.0/movies/search?q=man&limit=10&offset=20
* Movies matching "star" ordered by year of release with most recent first: http://localhost:8086/api/1.0/movies/search?q=star&sortby=release_year&ascending=false
* Details for movie with ID 293 (The Empire Strikes Back): http://localhost:8086/api/1.0/movies/293
* Aggregate query for movies by genre: http://localhost:8086/api/1.0/movies/group_by/genre



3,363 changes: 3,190 additions & 173 deletions sample-app/redisearch-node-rest/package-lock.json

Large diffs are not rendered by default.

25 changes: 14 additions & 11 deletions sample-app/redisearch-node-rest/package.json
Original file line number Diff line number Diff line change
@@ -3,32 +3,35 @@
"version": "1.0.0",
"description": "Sample RediSearch REST Server with Node.js",
"main": "server.js",
"type": "module",
"scripts": {
"start": "node server.js",
"test": "echo \"Error: no test specified\" && exit 1"
"dev": "./node_modules/nodemon/bin/nodemon.js",
Copy link

@leibale leibale Dec 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npm adds "./node_modules/.bin" to the path, "nodemon" should work (without the path)

"start": "node server.js"
},
"engines": {
"node": ">=8.9.4"
"node": ">=14.8.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Redis-Developer/getting-started-redisearch.git"
"url": "git+https://github.com/redisearch/redisearch-getting-started"
},
"keywords": [
"redis",
"redisearch",
"node"
],
"author": "Tugdual Grall",
"license": "Apache-2.0",
"author": "Redis",
"license": "MIT",
"bugs": {
"url": "https://github.com/Redis-Developer/getting-started-redisearch/issues"
"url": "https://github.com/redisearch/redisearch-getting-started/issues"
},
"homepage": "https://github.com/Redis-Developer/getting-started-redisearch#readme",
"homepage": "https://github.com/redisearch/redisearch-getting-started#readme",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1",
"redis": "^3.0.2",
"redis-redisearch": "stockholmux/node_redis-redisearch"
"express": "^4.18.1",
"redis": "^4.1.0"
},
"devDependencies": {
"nodemon": "^2.0.16"
}
}
93 changes: 25 additions & 68 deletions sample-app/redisearch-node-rest/server.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
const express = require('express');
const cors = require('cors');
const app = express();
const bodyParser = require('body-parser');
const serverPort = process.env.SERVER_PORT || 8086;

import express from 'express';
import cors from 'cors';
import SearchService from './NodeSearchService.js';

const app = express();
app.use(cors());
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

const SearchService = require('./NodeSearchService');
const serverPort = process.env.SERVER_PORT || 8086;
const searchService = new SearchService();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(bodyParser.json());

app.get('/api/1.0/movies/search', (req, res) => {
app.get('/api/1.0/movies/search', async (req, res) => {
const queryString = req.query.q;
const offset = Number((req.query.offset)?req.query.offset:'0');
const limit = Number((req.query.limit)?req.query.limit:'10');
const offset = Number((req.query.offset) ? req.query.offset : 0);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to req.query.offset ?? 0 (it was added in node 14.0.0 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing#browser_compatibility)

edit: and validate it's a positive number

const limit = Number((req.query.limit) ? req.query.limit : 10);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to req.query.limit ?? 10

edit: and validate it's not over 500 (or any other number)

const sortBy = req.query.sortby;
const ascending = req.query.ascending;

@@ -27,72 +24,32 @@ app.get('/api/1.0/movies/search', (req, res) => {

if (sortBy) {
options.sortBy = sortBy;
options.ascending = true; // if sorted by default it is ascending
options.ascending = true; // Default to sort ascending.
}

if (ascending) {
options.ascending = (ascending==1 || ascending.toLocaleLowerCase()==='true');
options.ascending = (ascending ==1 || ascending.toLocaleLowerCase() === 'true');
}

searchService.search(
queryString, // query string
options, // options
function(err, result){ // callback
res.json(result);
}
);
})

app.get('/api/1.0/movies/group_by/:field', (req, res) =>{
searchService.getMovieGroupBy(req.params.field, function(err, result){
res.json(result);
});
return res.json(await searchService.search(queryString, options));
});

app.get('/api/1.0/movies/:id', (req, res) =>{
searchService.getMovie(req.params.id, function(err, result){
res.json(result);
});
});
app.get('/api/1.0/movies/group_by/:field', async (req, res) => res.json(await searchService.getMovieGroupBy(req.params.field)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://www.npmjs.com/package/express-promise-router can use that to clean the code a bit


app.post('/api/1.0/movies/:id', (req, res) =>{
searchService.saveMovie(req.params.id, req.body, function(err, result){
res.json(result);
});
});
app.get('/api/1.0/movies/:id', async (req, res) => res.json(await searchService.getMovie(req.params.id)));

app.get('/api/1.0/movies/:id/comments', (req, res) =>{
searchService.getComments(req.params.id, {}, function(err, result){
res.json(result);
});
});
app.post('/api/1.0/movies/:id', async (req, res) => res.json(await searchService.saveMovie(req.params.id, req.body)));

app.post('/api/1.0/movies/:id/comments', (req, res) =>{
searchService.saveComment(req.params.id, req.body, function(err, result){
res.json(result);
});
});
app.get('/api/1.0/movies/:id/comments', async (req, res) => res.json(await searchService.getComments(req.params.id, {})));

app.get('/api/1.0/comments/:id', (req, res) =>{
searchService.getCommentById(req.params.id, function(err, result){
res.json(result);
});
});
app.post('/api/1.0/movies/:id/comments', async (req, res) => res.json(await searchService.saveComment(req.params.id, req.body)));

app.delete('/api/1.0/comments/:id', (req, res) =>{
searchService.deleteComment(req.params.id, function(err, result){
res.json(result);
});
});
app.get('/api/1.0/comments/:id', async (req, res) => res.json(await searchService.getCommentById(req.params.id)));

app.get('/api/1.0/', (req, res) => {
res.json({status: 'started'});
});
app.delete('/api/1.0/comments/:id', async (req, res) => res.json(await searchService.deleteComment(req.params.id)));

app.get('/api/1.0/', (req, res) => res.json({ status: 'started' }));

app.get('/', (req, res) => {
res.send('RediSearch Node REST Server Started');
});
app.get('/', (req, res) => res.send('RediSearch Node REST Server Started'));

app.listen(serverPort, () => {
console.log(`RediSearch Node listening at http://localhost:${serverPort}`);
});
app.listen(serverPort, () => console.log(`RediSearch Node REST Server listening at http://localhost:${serverPort}`));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the callback might get an error argument..