<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Jessica Garson - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Jessica Garson - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/search-labs/author/jessica-garson</link>
    </image>
    <link>https://www.elastic.co/search-labs/author/jessica-garson</link>
    <atom:link href="https://www.elastic.co/search-labs/rss/author/jessica-garson.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Fri, 18 Sep 2026 18:11:49 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Automatically updating your Elasticsearch index using Node.js and an Azure Function App]]></title>
    <description><![CDATA[Learn how to update your Elasticsearch index automatically using Node.js and an Azure Function App. Follow these steps to ensure your index stays current.]]></description>
    <content:encoded><![CDATA[<p>Maintaining an up-to-date Elasticsearch index is crucial, especially when dealing with frequently changing dynamic datasets. This blog post will guide you through automatically updating your Elasticsearch index using Node.js and an Azure Function App.</p><p>First, we'll load the data using Node.js and ensure it remains current through regular updates. Then, we'll leverage the capabilities of <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-javascript">Azure Function Apps</a> to automate these updates, thereby ensuring your index is always fresh and reliable.</p><p>For this blog post, we will be using the <a href="https://data.nasa.gov/Space-Science/Asteroids-NeoWs-API/73uw-d9i8/about_data">Near Earth Object Web Service (NeoWs</a>), a RESTful web service offering detailed information about near-earth asteroids. By integrating NeoWs with Node.js services integrated as Azure serverless functions, this example will provide you with a robust framework to handle the complexities of managing dynamic data effectively. This approach will help you minimize the risks of working with outdated information and maximize the accuracy and usefulness of your data.</p><h2>Prerequisites</h2><ul><li><p>This example uses Elasticsearch version 8.13; if you are new to Elasticsearch, check out our Quick Start on <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html">Elasticsearch</a>. Any 8.0 version should work for this blog post.</p></li><li><p>Download the latest <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm">NPM and Node.js version</a>. This tutorial uses Node v21.6.1 and npm 10.5.0.</p></li><li><p><a href="https://api.nasa.gov/">An API key</a> for NASA's APIs.</p></li><li><p>An active <a href="https://azure.microsoft.com/en-us/">Azure account</a> with access to create a Function App.</p></li><li><p>Access to the <a href="https://azure.microsoft.com/en-us/get-started/azure-portal">Azure portal</a> or <a href="https://learn.microsoft.com/en-us/cli/azure/">Azure CLI</a></p></li></ul><h2>Setting up locally</h2><p>Before you begin indexing and loading your data locally, setting up your environment is essential. First, create a directory and initialize it. Then, download the necessary packages and create a <code>.env</code> file to store your configuration settings. This preliminary setup ensures your local environment is prepared to handle the data efficiently.</p>mkdir Introduction-to-Data-Loading-in-Elasticsearch-with-Nodejs
cd Introduction-to-Data-Loading-in-Elasticsearch-with-Nodejs
npm init
<p>You will be using the <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html">Elasticsearch node client</a> to connect to Elastic, <a href="https://www.npmjs.com/package/axios">Axios</a> to connect to the NASA APIs and <a href="https://www.npmjs.com/package/dotenv">dotenv</a> to parse your secrets. You will want to download the required packages running the following commands:</p>npm install @elastic/elasticsearch axios dotenv
<p>After downloading the required packages, you can create a .<code>env</code> file at the root of the project directory. The .<code>env</code> file allows you to keep your credentials secure locally. Check out the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/automatically-updating-your-index-nodejs-azure/env.example">example .env file</a> to learn more. To learn more about connecting to Elasticsearch, be sure to take a look at the <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm">documentation on the subject</a>.</p><p>To create a <code>.env</code> file, you can use this command at the root of your project:</p>touch .env
<p>In your <code>.env </code>, be sure to have the following entered in. Be sure to add your complete endpoint:</p>ELASTICSEARCH_ENDPOINT="https://...."
ELASTICSEARCH_API_KEY="YOUR_ELASTICSEARCh_API_KEY"
NASA_API_KEY="YOUR_NASA_API_KEY"
<p>You will also want to create a new JavaScript file as well:</p>touch loading_data_into_a_index.js
<h2>Creating your index and loading your data in</h2><p>Now that you have set up the proper file structure and downloaded the required packages, you are ready to create a script that creates an index and loads data into the index. If you get stuck along the way be sure to check out the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/automatically-updating-your-index-nodejs-azure/loading_data_into_a_index.js">full version of the file</a> you are creating in this section.</p><p>In the file <code>loading_data_into_a_index.js,</code> configure the <a href="https://www.npmjs.com/package/dotenv">dotenv</a> package to use the keys and tokens stored in your .<code>env </code>file. You should also import the <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html">Elasticsearch client</a> to connect to Elasticsearch and <a href="https://www.npmjs.com/package/axios">Axios</a> and make HTTP requests.</p>require('dotenv').config();

const { Client } = require('@elastic/elasticsearch');
const axios = require('axios');
<p>Since your keys and tokens are currently stored as environment variables, you will want to retrieve them and create a client to authenticate to Elasticsearch.</p>const elasticsearchEndpoint = process.env.ELASTICSEARCH_ENDPOINT;
const elasticsearchApiKey = process.env.ELASTICSEARCH_API_KEY;
const nasaApiKey = process.env.NASA_API_KEY;

const client = new Client({
  node: elasticsearchEndpoint,
  auth: {
    apiKey: elasticsearchApiKey
  }
});
<p>You can develop a function to retrieve data from NASA's NEO (Near Earth Object) Web Service asynchronously. You will first configure the base URL for the NASA API request and create date objects for today and the previous week to establish the query period. After you format these dates in the YYYY-MM-DD format required for the API request, set up the dates as query parameters and execute the GET request to the NASA API. Additionally, the function includes error-handling mechanisms to aid debugging should any issues arise.</p>async function fetchNasaData() {
  const url = "https://api.nasa.gov/neo/rest/v1/feed";
  const today = new Date();
  const lastWeek = new Date(today);
  lastWeek.setDate(today.getDate() - 7);

  const startDate = lastWeek.toISOString().split('T')[0];
  const endDate = today.toISOString().split('T')[0];
  const params = {
    api_key: nasaApiKey,
    start_date: startDate,
    end_date: endDate,
  };

  try {
    const response = await axios.get(url, { params });
    return response.data;
  } catch (error) {
    console.error('Error fetching data from NASA:', error);
    return null;
  }
}
<p>Now, you can create a function to transform the raw data from the NASA API into a structured format. Since the data you get back is currently nested in a complex JSON response. A more straightforward array of objects makes handling data easier.</p>function createStructuredData(response) {
  const allObjects = [];
  const nearEarthObjects = response.near_earth_objects;

  Object.keys(nearEarthObjects).forEach(date =&gt; {
    nearEarthObjects[date].forEach(obj =&gt; {
      const simplifiedObject = {
        close_approach_date: date,
        name: obj.name,
        id: obj.id,
        miss_distance_km: obj.close_approach_data.length &gt; 0 ? obj.close_approach_data[0].miss_distance.kilometers : null,
      };

      allObjects.push(simplifiedObject);
    });
  });

  return allObjects;
}
<p>You will want to create an index to store the data from the API. An <a href="https://www.elastic.co/blog/what-is-an-elasticsearch-index">index</a> inside Elasticsearch is where you can store your data in documents. In this function, you will check to see if an index exists and create a new one if needed. You will also specify the proper <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">mapping</a> of fields for your index. This function also loads the data into the index as documents and maps the id field from the NASA data to the<code> _id</code> field in Elasticsearch.</p>async function indexDataIntoElasticsearch(data) {
  const indexExists = await client.indices.exists({ index: 'nasa-node-js' });
  if (!indexExists.body) {
    await client.indices.create({
      index: 'nasa-node-js',
      body: {
        mappings: {
          properties: {
            close_approach_date: { type: 'date' },
            name: { type: 'text' },
            miss_distance_km: { type: 'float' },
          },
        },
      },
    });
  }

  const body = data.flatMap(doc =&gt; [{ index: { _index: 'nasa-node-js', _id: doc.id } }, doc]);
  await client.bulk({ refresh: false, body });
}
<p>You will want to create a main function to fetch, structure, and index the data. This function will also print out the number of records being uploaded and log whether the data is indexed, whether there is no data to index, or whether it failed to get data back from the NASA API. After creating the <code>run</code> function, you will want to call the function and catch any errors that may come up.</p>async function run() {
  const rawData = await fetchNasaData();
  if (rawData) {
    const structuredData = createStructuredData(rawData);
    console.log(`Number of records being uploaded: ${structuredData.length}`);
    if (structuredData.length &gt; 0) {
      await indexDataIntoElasticsearch(structuredData);
      console.log('Data indexed successfully.');
    } else {
      console.log('No data to index.');
    }
  } else {
    console.log('Failed to fetch data from NASA.');
  }
}

run().catch(console.error);
<p>You can now run the file from your command line by running the following:</p>node loading_data_into_a_index.js
<p>To confirm that your index has been successfully loaded, you can check in the Elastic Dev Tools by executing the following API call:</p>GET /nasa-node-js/_search
<h2>Keeping your index updated with an Azure Function App</h2><p>Now that you've successfully loaded your data into your index locally, this data can quickly become outdated. To ensure your information remains current, you can set up an Azure Function App to automatically fetch new data daily and upload it to your Elasticsearch index.</p><p>The first step is to configure your Function app in Azure Portal. A helpful resource for getting started is the <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-create-function-app-portal?pivots=programming-language-javascript">Azure quick start guide</a>.</p><p>After you've set up your function, you can ensure that you have environment variables set up for <code>ELASTICSEARCH_ENDPOINT</code>, <code>ELASTICSEARCH_API_KEY</code>, and <code>NASA_API_KEY</code>. In Function Apps, environment variables are called Application settings. Inside your function app, click on the "Configuration" option in the left panel under "Settings." Under" the "Application settings" tab, click on "+ New application setting."</p><p>You will want to make sure the required libraries are installed as well. If you go to your terminal on the Azure Portal, you can install the necessary packages by entering the following:</p>npm install @elastic/elasticsearch axios
<p>The packages you are installing should look very similar to the previous install, except you will be using the moment to parse dates, and you no longer need to load an env file since you just set your secrets to be Application settings.</p><p>You can click where it says create to create a new function inside your Function App select the template entitled “Timer trigger”. You will now have a file called function.json set for you. You will want to adjust it to look as follows to run this application every day at 10 am.</p>{
    "bindings": [
      {
        "name": "myTimer",
        "type": "timerTrigger",
        "direction": "in",
        "schedule": "0 0 10 * * *"
      }
    ]
  }
<p>You'll also want to upload your <code>package.json</code> file and ensure it appears as follows:</p>{
  "name": "introduction-to-data-loading-in-elasticsearch-with-nodejs",
  "version": "1.0.0",
  "description": "A simple script for loading data in Elasticsearch",
  "main": "loading_data_into_a_index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/JessicaGarson/Introduction-to-Data-Loading-in-Elasticsearch-with-Nodejs.git"
  },
  "author": "Jessica Garson",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/JessicaGarson/Introduction-to-Data-Loading-in-Elasticsearch-with-Nodejs/issues"
  },
  "homepage": "https://github.com/JessicaGarson/Introduction-to-Data-Loading-in-Elasticsearch-with-Nodejs#readme",
  "dependencies": {
    "@elastic/elasticsearch": "^8.12.0",
    "axios": "^0.21.1"
  }
}
<p>The next step is to create a <code>index.js</code> file. This script is designed to automatically update the data daily. It accomplishes this by systematically fetching and parsing new data each day and then seamlessly updating the dataset accordingly. Elasticsearch can use the same method to ingest time series or immutable data, such as webhook responses. This method ensures the information remains current and accurate, reflecting the latest available data.You can can check out the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/automatically-updating-your-index-nodejs-azure/loading_data_into_a_index.js">full code</a> as well.</p><p>The main differences between the script you run locally and this one are as follows:</p><ul><li><p>You will no longer need to load a <code>.env</code> file, since you have already set your environment variables</p></li><li><p>There is also different logging designed more towards creating a more sustainable script</p></li><li><p>You keep your index updated based on the most recent <code>close approach date</code></p></li><li><p>There is an entry point for an Azure Function App</p></li></ul><p>You will first want to set up your libraries and authenticate to Elasticsearch as follows:</p>const elasticsearchEndpoint = process.env.ELASTICSEARCH_ENDPOINT;
const elasticsearchApiKey = process.env.ELASTICSEARCH_API_KEY;
const nasaApiKey = process.env.NASA_API_KEY;

const client = new Client({
 node: elasticsearchEndpoint,
 auth: {
   apiKey: elasticsearchApiKey
 }
});
<p>Afterward, you will want to obtain the last date update date from Elasticsearch and configure a backup method to get data from the past day if anything goes wrong.</p>async function getLastUpdateDate() {
  try {
    const response = await client.search({
      index: 'nasa-node-js',
      body: {
        size: 1,
        sort: [{ close_approach_date: { order: 'desc' } }],
        _source: ['close_approach_date']
      }
    });

    if (response.body &amp;&amp; response.body.hits &amp;&amp; response.body.hits.hits.length &gt; 0) {
      return response.body.hits.hits[0]._source.close_approach_date;
    } else {
      // Default to one day ago if no records found
      const today = new Date();
      const lastWeek = new Date(today);
      lastWeek.setDate(today.getDate() - 1);
      return lastWeek.toISOString().split('T')[0];
    }
  } catch (error) {
    console.error('Error fetching last update date from Elasticsearch:', error);
    throw error;
  }
}
<p>The following function connects to NASA's NEO (Near Earth Object) Web Service to get the data to keep your index updated. There is also some additional error handling that can capture any API errors that might come up.</p>async function fetchNasaData(startDate) {

  const url = "https://api.nasa.gov/neo/rest/v1/feed";
  const today = new Date();

  const endDate = today.toISOString().split('T')[0];

  const params = {
    api_key: nasaApiKey,
    start_date: startDate,
    end_date: endDate,
  };

  try {
    // Perform the GET request to the NASA API with query parameters
    const response = await axios.get(url, { params });
    return response.data;
  } catch (error) {
    // Log any errors encountered during the request
    console.error('Error fetching data from NASA:', error);
    return null;
  }
}
<p>Now, you will want to create a function to organize your data by iterating over the objects of each date.</p>function createStructuredData(response) {
  const allObjects = [];
  const nearEarthObjects = response.near_earth_objects;

  Object.keys(nearEarthObjects).forEach(date =&gt; {
    nearEarthObjects[date].forEach(obj =&gt; {
      const simplifiedObject = {
        close_approach_date: date,
        name: obj.name,
        id: obj.id,
        miss_distance_km: obj.close_approach_data.length &gt; 0 ? obj.close_approach_data[0].miss_distance.kilometers : null,
      };

      allObjects.push(simplifiedObject);
    });
  });

  return allObjects;
}
<p>Now, you will want to load your data into Elasticsearch using the bulk indexing operation. This function should look similar to the one in the previous section.</p>async function indexDataIntoElasticsearch(data) {
  const body = data.flatMap(doc =&gt; [{ index: { _index: 'nasa-node-js', _id: doc.id } }, doc]);
  await client.bulk({ refresh: false, body });
}
<p>Finally, you will want to create an entry point for the function that will run according to the timer you set. This function is similar to a main function, as it calls the functions created previously in the file. There is also some additional logging, such as printing the number of records and informing you if the data was indexed correctly.</p>module.exports = async function (context, myTimer) {
  try {
    const lastUpdateDate = await getLastUpdateDate();
    context.log(`Last update date from Elasticsearch: ${lastUpdateDate}`);

    const rawData = await fetchNasaData(lastUpdateDate);
    if (rawData) {
      const structuredData = createStructuredData(rawData);
      context.log(`Number of records being uploaded: ${structuredData.length}`);
      
      if (structuredData.length &gt; 0) {

        const flatFileData = JSON.stringify(structuredData, null, 2);
        context.log('Flat file data:', flatFileData);

        await indexDataIntoElasticsearch(structuredData);
        context.log('Data indexed successfully.');
      } else {
        context.log('No data to index.');
      }
    } else {
      context.log('Failed to fetch data from NASA.');
    }
  } catch (error) {
    context.log('Error in run process:', error);
  }
<h2>Conclusion</h2><p>Using Node.js and <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-javascript">Azure's Function App</a>, you should be able to ensure that your Elasticsearch index is updated regularly. By utilizing Node.js's capabilities in conjunction with Azure's Function App, you can efficiently maintain your index's regular updates. This powerful combination offers a streamlined, automated process, reducing the manual effort involved in keeping your index regularly updated. Full code for this example can be found on <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/automatically-updating-your-index-nodejs-azure">Search Labs GitHub</a>. Let us know if you built anything based on this blog or if you have questions on our <a href="https://discuss.elastic.co/">forums</a> and <a href="https://communityinviter.com/apps/elasticstack/elastic-community">the community Slack channel</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-index-node-js-automatic-updates</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-index-node-js-automatic-updates</guid>
    <category><![CDATA[Javascript]]></category>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[Jessica Garson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt373f9290a1371dd7/6a17122e0c48579b2001abba/fd87bff40e296ebce871d631c86fd0245f11c796-1440x960.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 04 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Keeping your Elasticsearch index current with Python and Google Cloud Platform Functions]]></title>
    <description><![CDATA[Keep your Elasticsearch index updated with Python &amp; Google Cloud Functions. Follow these steps to automatically update an index when new data is present.]]></description>
    <content:encoded><![CDATA[<h2>Background</h2><p>An <a href="https://www.elastic.co/blog/what-is-an-elasticsearch-index">index</a> inside Elasticsearch is where you can store your data in documents. While working with an index, the data can quickly grow old if you are working with a dynamic dataset. To avoid this issue, you can create a Python script to update your index and deploy it using <a href="https://cloud.google.com/">Google Cloud Platform's</a> (GCP) <a href="https://cloud.google.com/functions/docs#docs">Cloud Functions </a>and <a href="https://cloud.google.com/scheduler/docs">Cloud Scheduler</a> in order to keep your index up-to-date automatically.</p><p>To keep your index current, you can first set up a Jupyter Notebook to test locally and create a framework of a script that will update your index if new information is present. You can adjust your script to make it more reusable and run it as a Cloud Function. With Cloud Scheduler, you can set the code in your Cloud Function to run on a schedule using a cron-type format.</p><h2>Prerequisites for automating index updates</h2><ul><li><p>This example uses Elasticsearch version 8.12; if you are new, check out our Quick Start on <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html">Elasticsearch</a>.</p></li><li><p>Download the latest version of Python if you don't have it installed on your machine. This example utilizes Python 3.12.1.</p></li><li><p><a href="https://api.nasa.gov/">An API key</a> for NASA's APIs.</p></li><li><p>You will use the <a href="https://requests.readthedocs.io/en/latest/">Requests</a> package to connect to a NASA API, <a href="https://pandas.pydata.org/">Pandas</a> to manipulate data, the <a href="https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/getting-started-python.html">Elasticsearch Python Client</a> to load data into an index and keep it up to date, and <a href="https://docs.jupyter.org/en/latest/">Jupyter Notebooks</a> to work with your data interactively while testing. You can run the following line to install these required packages:</p></li></ul>pip3 install requests pandas elasticsearch notebook
<h2>Loading and updating your dataset</h2><p>Before you can run your update script inside of GCP, you will want to upload your data and test the process you will use to keep your script updated. You will first connect to data from an API, save it as a Pandas DataFrame, connect to Elasticsearch, upload the DataFrame into an index, check to see when the index is last updated, and update it if new data is available. You can find the complete code of this section in <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/keeping-your-index-current/local_testing.ipynb">this search labs notebook</a>.</p><h3>Loading your data</h3><p>Let's start testing locally with a Jupyter Notebook to work with your data interactively. To do so, you can run the following in your terminal.</p>jupyter notebook
<p>In the right-hand corner, you can select where it says “New” to create a new Jupyter Notebook.</p><p>First, you will need to import the packages you will be using. You will import all the packages you installed earlier, plus <code>getpass</code> to work with secrets such as API keys and <code>datetime</code> to work with date objects.</p>import requests
from getpass import getpass
import pandas as pd
from datetime import datetime, timedelta
from elasticsearch import Elasticsearch, helpers
<p>The dataset you will use is<a href="https://data.nasa.gov/Space-Science/Asteroids-NeoWs-API/73uw-d9i8/about_data"> Near Earth Object Web Service (NeoWs)</a>, a RESTful web service that provides near-earth Asteroid information. This dataset lets you search for asteroids based on their closest approach date to Earth, look up a specific asteroid, and browse the overall dataset.</p><p>With the following function, you can connect to NASA's NeoWs API, get data from the past week, and convert your response to a JSON object.</p>def connect_to_nasa():
    url = "https://api.nasa.gov/neo/rest/v1/feed"
    nasa_api_key = getpass("NASA API Key: ")
    today = datetime.now()
    params = {
        "api_key": nasa_api_key,
        "start_date": today - timedelta(days=7),
        "end_date": datetime.now(),
    }
    return requests.get(url, params).json()
<p>Now, you can save the results of your API call to a variable called response.</p>response = connect_to_nasa()
<p>To convert the JSON object into a pandas DataFrame, you must normalize the nested objects into one DataFrame and drop the column containing the nested JSON.</p>def create_df(response):
    all_objects = []
    for date, objects in response["near_earth_objects"].items():
        for obj in objects:
            obj["close_approach_date"] = date
            all_objects.append(obj)
    df = pd.json_normalize(all_objects)
    return df.drop("close_approach_data", axis=1)
<p>To call this function and view the first five rows of your dataset, you can run the following:</p>df = create_df(response)
df.head()
<h3>Connecting to Elasticsearch</h3><p>You can access Elasticsearch from the Python Client by providing your Elastic Cloud ID and API key for authentication.</p>def connect_to_elastic():
    elastic_cloud_id = getpass("Elastic Cloud ID: ")
    elastic_api_key = getpass("Elastic API Key: ")
    return Elasticsearch(cloud_id=elastic_cloud_id, api_key=elastic_api_key)
<p>Now, you can save the results of your connection function to a variable called <code>es</code>.</p>es = connect_to_elastic()
<p>An index in Elasticsearch is the main container for your data. You can name your index called <code>asteroid_data_set</code>.</p>index_name = "asteroid_data_set"
es.indices.create(index=index_name)
<p>The result you get back will look like the following:</p>ObjectApiResponse({'acknowledged': True, 'shards_acknowledged': True, 'index': 'asteroids_data'})
<p>Now, you can create a helper function that will allow you to convert your DataFrame to the correct format to upload into your index.</p>def doc_generator(df, index_name):
    for index, document in df.iterrows():
        yield {
            "_index": index_name,
            "_id": f"{document['id']}",
            "_source": document.to_dict(),
        }
<p>Next, you can bulk upload the contents of your DataFrame into Elastic, calling the helper function you just created.</p>helpers.bulk(es, doc_generator(df, index_name))
<p>You should get a result that looks similar to the following, which tells you how many rows you’ve uploaded:</p>(146, [])
<h3>When was the last time you updated your data?</h3><p>Once you've uploaded data into Elastic, you can check the last time your index was updated and format the date so it can work with NASA API.</p>def updated_last(es, index_name):
    query = {
        "size": 0,
        "aggs": {"last_date": {"max": {"field": "close_approach_date"}}},
    }
    response = es.search(index=index_name, body=query)
    last_updated_date_string = response["aggregations"]["last_date"]["value_as_string"]
    datetime_obj = datetime.strptime(last_updated_date_string, "%Y-%m-%dT%H:%M:%S.%fZ")
    return datetime_obj.strftime("%Y-%m-%d")
<p>You can save the date your index was last updated to a variable and print out the date.</p>last_update_date = updated_last(es, index_name)
print(last_update_date)
<h3>Updating your data</h3><p>Now, you can create a function that checks to see if there is any new data since the last time the index was updated and the current date. If the object is valid and the data is not empty, it will update the index and let you know if there is no new data to update or if the DataFrame returns a type of <code>None</code> indicating that there may have been a problem.</p>def update_new_data(df, es, last_update_date, index_name):
    if isinstance(last_update_date, str):
        last_update_date = datetime.strptime(last_update_date, "%Y-%m-%d")

    last_update_date = pd.Timestamp(last_update_date).normalize()

    if not df.empty and "close_approach_date" in df.columns:
        df["close_approach_date"] = pd.to_datetime(df["close_approach_date"])

    today = pd.Timestamp(datetime.now().date()).normalize()

    if df is not None and not df.empty:
        update_range = df.loc[
            (df["close_approach_date"] &gt; last_update_date)
            &amp; (df["close_approach_date"] &lt; today)
        ]
        if not update_range.empty:
            helpers.bulk(es, doc_generator(update_range, index_name))
        else:
            print("No new data to update.")
    else:
        print("The DataFrame is None.")
<p>If the DataFrame is a valid object, it will call the function you wrote and update the index if applicable. It will also print out the date of the index's last update to help you debug if needed. If not, it will tell you there may be a problem.</p>try:
    if df is None:
        raise ValueError("DataFrame is None. There may be a problem.")
    update_new_data(df, es, last_update_date, index_name)
    print(updated_last(es, index_name))
except Exception as e:
    print(f"An error occurred: {e}")
<h2>Keeping your index current</h2><p>Now that you've created a framework for local testing, you are ready to set up an environment where you can run your script daily to check to see if any new data is available and update your index accordingly.</p><h3>Creating a Cloud Function</h3><p>You are now ready to deploy your Cloud Function. To do so, you will want to select the environment as a 2nd gen function, name your function, and select a cloud region. You can also tie it to a Cloud Pub/Sub trigger and choose to create a new topic if you haven't made it already. You can check out the <a href="https://github.com/JessicaGarson/Keeping-Your-Elasticsearch-Index-Current">complete code for this section on GitHub</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd01cb110679df057/6a170b30ab7f084f76db9e9a/5e5548faac7d7ed20166e853db9a81d4ed51d60b-1116x1188.jpg" alt="" /><h3>Creating a Pub/Sub topic</h3><p>When creating a new topic, you can name your topic ID and select the encryption using a Google-managed encryption key.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93a679b113dc8eee/6a170b321949f751eee7aa3e/5a32c2efaf32f496fa201d3c3e7f97de29b59f66-1114x874.jpg" alt="" /><h3>Setting your Cloud Function's environment variables</h3><p>Under where it says “Runtime environment variables,” you can add in the environment variables for your <code>NASA_API_KEY,</code> <code>ELASTIC_CLOUD_ID</code>, and <code>ELASTIC_API_KEY.</code> You will want to save these as the raw values without single quotes around them. So if you entered a value of <code>'xxxxlsdgzxxxxx'</code> into your terminal earlier, you would want it to be <code>xxxxlsdgzxxxxx</code>.</p><h3>Adjusting your code and adding it to your Cloud Function</h3><p>After you enter your environment variables, you can press the button that says next, which will take you to a code editor. You will want to select the runtime of Python 3.12.1 or match the version of Python you are using. After that, update the entry point to <code>update_index</code>. The entry point serves a similar role to the main function in Python.</p><p>Instead of using <code>getpass</code> to retrieve secrets, you will want to use <code>os</code> to perform a more automated process. An example will look like the following:</p>elastic_cloud_id = os.getenv("ELASTIC_CLOUD_ID")
elastic_api_key = os.getenv("ELASTIC_API_KEY")
<p>You will want to adjust the order of your script to have the function that connects to Elasticsearch first. Afterward, you will want to know when your index was last updated, connect to the NASA API you are using, save it to DataFrame, and load any new data that might be available.</p><p>You may notice a new function at the bottom called <code>update_index</code> that ties your code together. In this function, you define the name of your index, connect to Elastic, figure out the last date the index was updated, connect to the NASA API, save the results into a data frame, and update the index if needed. To indicate the entry point function is a cloud event, you can denote it with the decorator <code>@functions_framework.cloud_event</code>.</p>@functions_framework.cloud_event
def update_index(cloud_event):
    index_name = "asteroid_data_set"
    es = connect_to_elastic()
    last_update_date = updated_last(es, index_name)
    print(last_update_date)
    response = connect_to_nasa(last_update_date)
    df = create_df(response)
    if df is not None:
      update_new_data(df, es, last_update_date, index_name)
      print(updated_last(es, index_name)) 
    else:
      print("No new data was retrieved.")
<p>Here is the full updated code sample:</p>import functions_framework
import requests
import os
import pandas as pd
from datetime import datetime
from elasticsearch import Elasticsearch, helpers


def connect_to_elastic():
    elastic_cloud_id = os.getenv("ELASTIC_CLOUD_ID")
    elastic_api_key = os.getenv("ELASTIC_API_KEY")
    return Elasticsearch(cloud_id=elastic_cloud_id, api_key=elastic_api_key)


def connect_to_nasa(last_update_date):
    url = "https://api.nasa.gov/neo/rest/v1/feed"
    nasa_api_key = os.getenv("NASA_API_KEY")
    params = {
        "api_key": nasa_api_key,
        "start_date": last_update_date,
        "end_date": datetime.now(),
    }
    return requests.get(url, params).json()


def create_df(response):
    all_objects = []
    for date, objects in response["near_earth_objects"].items():
        for obj in objects:
            obj["close_approach_date"] = date
            all_objects.append(obj)
    df = pd.json_normalize(all_objects)
    return df.drop("close_approach_data", axis=1)


def doc_generator(df, index_name):
    for index, document in df.iterrows():
        yield {
            "_index": index_name,
            "_id": f"{document['close_approach_date']}",
            "_source": document.to_dict(),
        }


def updated_last(es, index_name):
    query = {
        "size": 0,
        "aggs": {"last_date": {"max": {"field": "close_approach_date"}}},
    }
    response = es.search(index=index_name, body=query)
    last_updated_date_string = response["aggregations"]["last_date"]["value_as_string"]
    datetime_obj = datetime.strptime(last_updated_date_string, "%Y-%m-%dT%H:%M:%S.%fZ")
    return datetime_obj.strftime("%Y-%m-%d")


def update_new_data(df, es, last_update_date, index_name):
    if isinstance(last_update_date, str):
        last_update_date = datetime.strptime(last_update_date, "%Y-%m-%d")

    last_update_date = pd.Timestamp(last_update_date).normalize()

    if not df.empty and "close_approach_date" in df.columns:
        df["close_approach_date"] = pd.to_datetime(df["close_approach_date"])

    today = pd.Timestamp(datetime.now().date()).normalize()

    if df is not None and not df.empty:
        update_range = df.loc[
            (df["close_approach_date"] &gt; last_update_date)
            &amp; (df["close_approach_date"] &lt; today)
        ]
        print(update_range)
        if not update_range.empty:
            helpers.bulk(es, doc_generator(update_range, index_name))
        else:
            print("No new data to update.")
    else:
        print("The DataFrame is empty or None.")


# Triggered from a message on a Cloud Pub/Sub topic.
@functions_framework.cloud_event
def hello_pubsub(cloud_event):
    index_name = "asteroid_data_set"
    es = connect_to_elastic()
    last_update_date = updated_last(es, index_name)
    print(last_update_date)
    response = connect_to_nasa(last_update_date)
    df = create_df(response)
    try:
        if df is None:
            raise ValueError("DataFrame is None. There may be a problem.")
        update_new_data(df, es, last_update_date, index_name)
        print(updated_last(es, index_name))
    except Exception as e:
        print(f"An error occurred: {e}")
<h3>Adding a requirements.txt file</h3><p>You will also want to define a <code>requirements.txt</code> file with all the specified packages needed to run the code.</p>functions-framework==3.*
requests==2.31.0
elasticsearch==8.12.0
pandas==2.1.4
<h3>Scheduling your Cloud Function</h3><p>In Cloud Scheduler, you can set up your function to run at a regular interval using unix cron format. I have the code set to run every morning at 8 am in my timezone.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt031810158f88fd08/6a170b34a929cf4718ae09d0/a6b9c490a804389ec15366ff96adb9c992cb561a-1160x1014.jpg" alt="" /><p>You will also want to configure the execution to connect to the Pub/Sub topic you created previously. I currently have the message body set to say “hello.”</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5c6c0061dc955fc/6a170b35b0367d9d5672bd37/b12a4a782976edbf9b6a65992f0d3d73df2d15c0-1116x518.jpg" alt="" /><p>Now that you have set up your Pub/Sub topic and your Cloud Function and set that Cloud Function to run on a schedule, your index should automatically update whenever new data is present.</p><h2>Conclusion</h2><p>Using Python, Google Cloud Platform Functions, and Google Cloud Scheduler you should be able to ensure that your index is updated regularly. You can find the complete code <a href="https://github.com/JessicaGarson/Keeping-Your-Elasticsearch-Index-Current">here</a> and <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/keeping-your-index-current/local_testing.ipynb">the search labs notebook for local testing</a>. We are also running an on-demand webinar with <a href="https://www.elastic.co/virtual-events/architecting-search-apps-on-google-cloud">Google Cloud</a> which might be a good next step if you are looking to build search apps. Let us know if you built anything based on this blog or if you have questions on our <a href="https://discuss.elastic.co/">Discuss forums</a> and <a href="https://communityinviter.com/apps/elasticstack/elastic-community">the community Slack channel</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/keeping-your-elasticsearch-index-current-with-python-and-google-cloud-platform-functions</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/keeping-your-elasticsearch-index-current-with-python-and-google-cloud-platform-functions</guid>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[Basics]]></category>
    <dc:creator><![CDATA[Jessica Garson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt315cf663d39ca57f/6a170b3760084b95c23c4576/b839822139ab0769a7fcf1d62102c984af87bf0d-1440x954.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 13 Mar 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>