Added Elasticsearch proxy example (#1398)
Co-authored-by: István Zoltán Szabó <istvan.szabo@elastic.co>
This commit is contained in:
committed by
GitHub
parent
2494f08ad2
commit
9e3072f621
51
docs/examples/proxy/.gitignore
vendored
Normal file
51
docs/examples/proxy/.gitignore
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# coverage output
|
||||
coverage.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# mac files
|
||||
.DS_Store
|
||||
|
||||
# vim swap files
|
||||
*.swp
|
||||
|
||||
#Jetbrains editor folder
|
||||
.idea
|
||||
|
||||
.vercel
|
||||
65
docs/examples/proxy/README.md
Normal file
65
docs/examples/proxy/README.md
Normal file
@ -0,0 +1,65 @@
|
||||
# Elasticsearch proxy example
|
||||
|
||||
This folder contains an example of how to build a lightweight proxy
|
||||
between your frontend code and Elasticsearch if you don't
|
||||
have a more sophisticated backend in place yet.
|
||||
|
||||
> **IMPORTANT:** This is not a production ready code and it is only for demonstration purposes,
|
||||
> we make no guarantees on it's security and stability.
|
||||
|
||||
This project is designed to be deployed on [Vercel](https://vercel.com/), a cloud platform
|
||||
for static sites and Serverless Functions. You can use other functions providers,
|
||||
such as [Google Cloud functions](https://cloud.google.com/functions).
|
||||
|
||||
## Project structure
|
||||
|
||||
The project comes with four endpoints:
|
||||
|
||||
- `/api/search`: runs a search, requires `'read'` permission
|
||||
- `/api/autocomplete`: runs an autocomplete suggestion, requires `'read'` permission
|
||||
- `/api/index`: indexes or updates a document, requires `'write'` permission
|
||||
- `/api/delete`: deletes a document, requires `'write'` permission
|
||||
|
||||
Inside `utils/authorize.js` you can find the authorization logic for the endpoints.
|
||||
In each endpoint you should configure the `INDEX` variable.
|
||||
|
||||
## How to use
|
||||
|
||||
Create an account on Vercel, then create a deployment on Elastic Cloud. If you
|
||||
don't have an account on Elastic Cloud, you can create one with a free 14-day trial
|
||||
of the [Elasticsearch Service](https://www.elastic.co/elasticsearch/service).
|
||||
|
||||
### Configure Elasticsearch
|
||||
|
||||
Once you have created a deployment on Elastic Cloud copy the generated Cloud Id and the credentials.
|
||||
Then open `utils/prepare-elasticsearch.js` and fill your credentials. The script generates
|
||||
an [Api Key](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html)
|
||||
that you can use for authenticating your request. Based on the configuration of the Api Key, you will be able
|
||||
to perform different operation on the specified indices or index pattern.
|
||||
|
||||
### Configure Vercel
|
||||
|
||||
Install the [Vercel CLI](https://vercel.com/docs/cli) to bootstrap the project,
|
||||
or read the [quickstart](https://vercel.com/docs) documentation.
|
||||
|
||||
If you are using the CLI, bootstrap the project by running `vercel`. Test the project locally
|
||||
with `vercel dev`, and deploy it with `vercel deploy`.
|
||||
Configure the `ELASTIC_CLOUD_ID` [environment varible](https://vercel.com/docs/environment-variables) as well.
|
||||
The Api Key is passed from the frontend app via a `Authorization` header as `Bearer` token and is
|
||||
used to authorize the API calls to the endpoints as well.
|
||||
Additional configuration, such as CORS, can be added to [`vercel.json`](https://vercel.com/docs/configuration).
|
||||
|
||||
## Authentication
|
||||
|
||||
If you are using Elasticsearch only for search purposes, such as a search box, you can create
|
||||
an Api Key with `read` permissions and store it in your frontend app. Then you can send it
|
||||
via `Authorization` header to the proxy and run your searches.
|
||||
|
||||
If you need to ingest data as well, it's more secure to have a strong authentication in your application.
|
||||
For such cases, use an external authentication service, such as [Auth0](https://auth0.com/)
|
||||
or [Magic Link](https://magic.link/). Then create a different Api Key with `read` and `write`
|
||||
permissions for authenticated users, that will not be stored in the frontend app.
|
||||
|
||||
## License
|
||||
|
||||
This software is licensed under the [Apache 2 license](../../LICENSE).
|
||||
105
docs/examples/proxy/api/autocomplete.js
Normal file
105
docs/examples/proxy/api/autocomplete.js
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// IMPORTANT: this is not a production ready code & purely for demonstration purposes,
|
||||
// we make no guarantees on it's security and stability
|
||||
|
||||
// NOTE: to make this endpoint work, you should create an ApiKey with 'read' permissions
|
||||
|
||||
'use strict'
|
||||
|
||||
const { Client } = require('@elastic/elasticsearch')
|
||||
const authorize = require('../utils/authorize')
|
||||
|
||||
const INDEX = '<index-name>'
|
||||
const client = new Client({
|
||||
cloud: {
|
||||
id: process.env.ELASTIC_CLOUD_ID
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
const [err, token] = authorize(req)
|
||||
if (err) {
|
||||
res.status(401)
|
||||
res.json(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof req.query.q !== 'string') {
|
||||
res.status(400)
|
||||
res.json({
|
||||
error: 'Bad Request',
|
||||
message: 'Missing parameter "query.q"',
|
||||
statusCode: 400
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (req.query.q.length < 3) {
|
||||
res.status(400)
|
||||
res.json({
|
||||
error: 'Bad Request',
|
||||
message: 'The length of "query.q" should be at least 3',
|
||||
statusCode: 400
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.search({
|
||||
index: INDEX,
|
||||
// You could directly send from the browser
|
||||
// the Elasticsearch's query DSL, but it will
|
||||
// expose you to the risk that a malicious user
|
||||
// could overload your cluster by crafting
|
||||
// expensive queries.
|
||||
body: {
|
||||
_source: ['id', 'url', 'name'], // the fields you want to show in the autocompletion
|
||||
size: 0,
|
||||
// https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
|
||||
suggest: {
|
||||
suggestions: {
|
||||
prefix: req.query.q,
|
||||
completion: {
|
||||
field: 'suggest',
|
||||
size: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `ApiKey ${token}`
|
||||
}
|
||||
})
|
||||
|
||||
// It might be useful to configure http control caching headers
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||
// res.setHeader('stale-while-revalidate', '30')
|
||||
res.json(response.body)
|
||||
} catch (err) {
|
||||
res.status(err.statusCode || 500)
|
||||
res.json({
|
||||
error: err.name,
|
||||
message: err.message,
|
||||
statusCode: err.statusCode || 500
|
||||
})
|
||||
}
|
||||
}
|
||||
74
docs/examples/proxy/api/delete.js
Normal file
74
docs/examples/proxy/api/delete.js
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// IMPORTANT: this is not a production ready code & purely for demonstration purposes,
|
||||
// we make no guarantees on it's security and stability
|
||||
|
||||
// NOTE: to make this endpoint work, you should create an ApiKey with 'write' permissions
|
||||
|
||||
'use strict'
|
||||
|
||||
const { Client } = require('@elastic/elasticsearch')
|
||||
const authorize = require('../utils/authorize')
|
||||
|
||||
const INDEX = '<index-name>'
|
||||
const client = new Client({
|
||||
cloud: {
|
||||
id: process.env.ELASTIC_CLOUD_ID
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
const [err, token] = authorize(req)
|
||||
if (err) {
|
||||
res.status(401)
|
||||
res.json(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof req.query.id !== 'string' && req.query.id.length === 0) {
|
||||
res.status(400)
|
||||
res.json({
|
||||
error: 'Bad Request',
|
||||
message: 'Missing document id',
|
||||
statusCode: 400
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.delete({
|
||||
index: INDEX,
|
||||
id: req.query.id
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `ApiKey ${token}`
|
||||
}
|
||||
})
|
||||
|
||||
res.json(response.body)
|
||||
} catch (err) {
|
||||
res.status(err.statusCode || 500)
|
||||
res.json({
|
||||
error: err.name,
|
||||
message: err.message,
|
||||
statusCode: err.statusCode || 500
|
||||
})
|
||||
}
|
||||
}
|
||||
76
docs/examples/proxy/api/index.js
Normal file
76
docs/examples/proxy/api/index.js
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// IMPORTANT: this is not a production ready code & purely for demonstration purposes,
|
||||
// we make no guarantees on it's security and stability
|
||||
|
||||
// NOTE: to make this endpoint work, you should create an ApiKey with 'write' permissions
|
||||
|
||||
'use strict'
|
||||
|
||||
const { Client } = require('@elastic/elasticsearch')
|
||||
const authorize = require('../utils/authorize')
|
||||
|
||||
const INDEX = '<index-name>'
|
||||
const client = new Client({
|
||||
cloud: {
|
||||
id: process.env.ELASTIC_CLOUD_ID
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
const [err, token] = authorize(req)
|
||||
if (err) {
|
||||
res.status(401)
|
||||
res.json(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof req.body !== 'object') {
|
||||
res.status(400)
|
||||
res.json({
|
||||
error: 'Bad Request',
|
||||
message: 'The document should be an object',
|
||||
statusCode: 400
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.index({
|
||||
index: INDEX,
|
||||
id: req.query.id,
|
||||
body: req.body
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `ApiKey ${token}`
|
||||
}
|
||||
})
|
||||
|
||||
res.status(response.statusCode)
|
||||
res.json(response.body)
|
||||
} catch (err) {
|
||||
res.status(err.statusCode || 500)
|
||||
res.json({
|
||||
error: err.name,
|
||||
message: err.message,
|
||||
statusCode: err.statusCode || 500
|
||||
})
|
||||
}
|
||||
}
|
||||
86
docs/examples/proxy/api/search.js
Normal file
86
docs/examples/proxy/api/search.js
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// IMPORTANT: this is not a production ready code & purely for demonstration purposes,
|
||||
// we make no guarantees on it's security and stability
|
||||
|
||||
// NOTE: to make this endpoint work, you should create an ApiKey with 'read' permissions
|
||||
|
||||
'use strict'
|
||||
|
||||
const { Client } = require('@elastic/elasticsearch')
|
||||
const authorize = require('../utils/authorize')
|
||||
|
||||
const INDEX = '<index-name>'
|
||||
const client = new Client({
|
||||
cloud: {
|
||||
id: process.env.ELASTIC_CLOUD_ID
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
const [err, token] = authorize(req)
|
||||
if (err) {
|
||||
res.status(401)
|
||||
res.json(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof req.body.text !== 'string') {
|
||||
res.status(400)
|
||||
res.json({
|
||||
error: 'Bad Request',
|
||||
message: 'Missing parameter "body.text"',
|
||||
statusCode: 400
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await client.search({
|
||||
index: INDEX,
|
||||
// You could directly send from the browser
|
||||
// the Elasticsearch's query DSL, but it will
|
||||
// expose you to the risk that a malicious user
|
||||
// could overload your cluster by crafting
|
||||
// expensive queries.
|
||||
body: {
|
||||
query: {
|
||||
match: { field: req.body.text }
|
||||
}
|
||||
}
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `ApiKey ${token}`
|
||||
}
|
||||
})
|
||||
|
||||
// It might be useful to configure http control caching headers
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||
// res.setHeader('stale-while-revalidate', '30')
|
||||
res.json(response.body)
|
||||
} catch (err) {
|
||||
res.status(err.statusCode || 500)
|
||||
res.json({
|
||||
error: err.name,
|
||||
message: err.message,
|
||||
statusCode: err.statusCode || 500
|
||||
})
|
||||
}
|
||||
}
|
||||
19
docs/examples/proxy/package.json
Normal file
19
docs/examples/proxy/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "proxy-example",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "standard"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Tomas Della Vedova",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@elastic/elasticsearch": "^7.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"standard": "^16.0.3"
|
||||
}
|
||||
}
|
||||
54
docs/examples/proxy/utils/authorize.js
Normal file
54
docs/examples/proxy/utils/authorize.js
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// IMPORTANT: this is not a production ready code & purely for demonstration purposes,
|
||||
// we make no guarantees on it's security and stability
|
||||
|
||||
'use strict'
|
||||
|
||||
module.exports = (req) => {
|
||||
const auth = req.headers.authorization
|
||||
if (typeof auth !== 'string') {
|
||||
return [{
|
||||
error: 'Unauthorized',
|
||||
message: 'Missing authorization header',
|
||||
statusCode: 401
|
||||
}, null]
|
||||
}
|
||||
|
||||
const [type, token] = req.headers.authorization.split(' ')
|
||||
|
||||
if (type !== 'Bearer') {
|
||||
return [{
|
||||
error: 'Unauthorized',
|
||||
message: 'Bad authorization type',
|
||||
statusCode: 401
|
||||
}, null]
|
||||
}
|
||||
|
||||
if (token.length === 0) {
|
||||
return [{
|
||||
error: 'Unauthorized',
|
||||
message: 'Bad authorization token',
|
||||
statusCode: 401
|
||||
}, null]
|
||||
}
|
||||
|
||||
return [null, token]
|
||||
}
|
||||
68
docs/examples/proxy/utils/prepare-elasticsearch.js
Normal file
68
docs/examples/proxy/utils/prepare-elasticsearch.js
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const { Client } = require('@elastic/elasticsearch')
|
||||
|
||||
// Your Cloud Id
|
||||
const cloudId = ''
|
||||
// Your admin username
|
||||
const username = ''
|
||||
// Your admin password
|
||||
const password = ''
|
||||
// The indices or index patterns you will need to access
|
||||
const indexNames = ['my-index-name-or-pattern']
|
||||
// see https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html#privileges-list-indices
|
||||
const privileges = ['read']
|
||||
|
||||
async function generateApiKeys (opts) {
|
||||
const client = new Client({
|
||||
cloud: {
|
||||
id: cloudId
|
||||
},
|
||||
auth: {
|
||||
username,
|
||||
password
|
||||
}
|
||||
})
|
||||
|
||||
const { body } = await client.security.createApiKey({
|
||||
body: {
|
||||
name: 'elasticsearch-proxy',
|
||||
role_descriptors: {
|
||||
'elasticsearch-proxy-users': {
|
||||
index: [{
|
||||
names: indexNames,
|
||||
privileges
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return Buffer.from(`${body.id}:${body.api_key}`).toString('base64')
|
||||
}
|
||||
|
||||
generateApiKeys()
|
||||
.then(console.log)
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
13
docs/examples/proxy/vercel.json
Normal file
13
docs/examples/proxy/vercel.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"headers": [
|
||||
{
|
||||
"source": "/api/(.*)",
|
||||
"headers": [
|
||||
{ "key": "Access-Control-Allow-Credentials", "value": "true" },
|
||||
{ "key": "Access-Control-Allow-Origin", "value": "*" },
|
||||
{ "key": "Access-Control-Allow-Methods", "value": "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
|
||||
{ "key": "Access-Control-Allow-Headers", "value": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -90,4 +90,5 @@ using.
|
||||
|
||||
WARNING: There is no official support for the browser environment. It exposes
|
||||
your {es} instance to everyone, which could lead to security issues. We
|
||||
recommend you to write a lightweight proxy that uses this client instead.
|
||||
recommend you to write a lightweight proxy that uses this client instead,
|
||||
you can see a proxy example https://github.com/elastic/elasticsearch-js/tree/master/docs/examples/proxy[here].
|
||||
Reference in New Issue
Block a user