Netlify Function adding / to end of URL of API request causing it to fail

As the title says, I’m trying to make an API request using a Netlify function but it’s adding a trailing / at the end of the URL which is causing the request to fail. The request uses two parameters, one for a date and the last one for an API key which is currently stored as an environment variable that I set within the Netlify admin. When I hit my React app that is utilizing this Netlify function, I get the following error in the admin (API key redacted by me)

Failed to load resource: the server responded with a status of 500 ()    /.netlify/functions/nasaPhotoKey?date=2022-07-27:1 
App.js:28 
Object
data:
code: "ERR_BAD_REQUEST"
config:
env: {}
headers: {Accept: 'application/json, text/plain, */*', User-Agent: 'axios/0.27.2'}
maxBodyLength: -1
maxContentLength: -1
method: "get"
timeout: 0
transformRequest: [null]
transformResponse: [null]
transitional: {silentJSONParsing: true, forcedJSONParsing: true, clarifyTimeoutError: false}
url: "https://api.nasa.gov/planetary/apod?date=2022-07-27&api_key=API_KEY_REDACTED/"
xsrfCookieName: "XSRF-TOKEN"
xsrfHeaderName: "X-XSRF-TOKEN"
[[Prototype]]: Object
message: "Request failed with status code 403"
name: "AxiosError"
status: 403
[[Prototype]]: Object
[[Prototype]]: Object

If I copy the URL from the above, paste it into the address bar, and delete the trailing slash before hitting enter it brings up the data that I’m expecting.

Here’s the code from my Netlify function:

// const { resolvePath } = require("react-router-dom");
// const fetch = require('node-fetch');
const url = 'https://api.nasa.gov/planetary/apod';
const apikey = process.env.REACT_APP_NASA_API;

// const axios = require('axios');
exports.handler = async (event, context) => {
  const date = event.queryStringParameters.date;
  let response;
  try {
    response = await fetch(`${url}?date=${date}&api_key=${apikey}`)
    // handle response
  } catch (err) {
    return {
      statusCode: err.statusCode || 500,
      body: JSON.stringify({
        error: err.message
      })
    }
  }

  return {
    statusCode: 200,
    body: JSON.stringify({
      data: response
    })
  }

//   const params = new URLSearchParams([
//     ['date', `${date}`],
//     ['api_key', `${apikay}`]
// ]);
//   
//   try {
//     const response = await axios.get(url, {params});
//     return {
//       statusCode: 200,
//       body: JSON.stringify(response.data)
//     }
//   }
//   catch(error){
//     return {
//       statusCode: 500,
//       body: JSON.stringify(error)
//     }
//   }
};

I have made sure that pretty URLs are turned off, that the variable for the API key doesn’t have the trailing slash saved in it, and I have tried a couple of different ways of making the call as you can see from the commented-out code.

I’ll admit that the point to doing this as a Netlify function was to try and secure the API key better, so I’m a bit concerned that it’s still printing it out to the console in the error message even though I’m hoping that’ll make it easier to troubleshoot. I haven’t had much luck searching for a solution on my own and am hoping that someone here will be kind enough to point me in the right direction.

This is not something Netlify is doing automatically. Is it possible for you to share a reproduction?

If you use axios is the behaviour same when you do something like:

axios({
  params: {
    apikey: process.env.REACT_APP_NASA_API,
    date: event.queryStringParameters.date
  },
  url: 'https://api.nasa.gov/planetary/apod'
})

I’m not familiar with that exact syntax, but when I use axios with parameters it still gives me the trailing slash. If the below isn’t correct, should I replace something in there with what you’ve provided?

const url = 'https://api.nasa.gov/planetary/apod';
const apikey = process.env.REACT_APP_NASA_API;

const axios = require('axios');
exports.handler = async (event, context) => {
  const date = event.queryStringParameters.date;
  let response;
  const params = new URLSearchParams([
    ['date', `${date}`],
    ['api_key', `${apikey}`]
]);
  // const url = 'https://api.nasa.gov/planetary/apod';
  try {
    const response = await axios.get(url, {params});
    return {
      statusCode: 200,
      body: JSON.stringify(response.data)
    }
  } catch (err) {
    return {
      statusCode: err.statusCode || 500,
      body: JSON.stringify({
        error: err.message
      })
    }
  }

//   const params = new URLSearchParams([
//     ['date', `${date}`],
//     ['api_key', `${apikay}`]
// ]);
//   
//   try {
//     const response = await axios.get(url, {params});
//     return {
//       statusCode: 200,
//       body: JSON.stringify(response.data)
//     }
//   }
//   catch(error){
//     return {
//       statusCode: 500,
//       body: JSON.stringify(error)
//     }
//   }
};

I’m not sure why you’re converting that to URLSearchParams.

The syntax I shared handles this automatically for you:

https://axios-http.com/docs/req_config

While it does support URLSearchParams object, I feel that’s an extra point of failure that you’re adding in your app and I’d personally leave it to Axios.

While fetch should have no issues too, I’m a little stumped to guess what could be the issue. This is exactly why I asked you to share a reproduction. While the above code appears to be a reproduction from your end, unfortunately it doesn’t trigger the same behaviour for us.

I’m pretty stumped too, which is why I posted - thanks for taking the time to look at this. I think I figured out where the code you provided was supposed to go, but it’s still putting a trailing slash in there.

Here’s what’s in my App.js in case that helps

//6/21/2022 has a smaller than average photo
//11/02/2015 might not have a photo
//04/21/2015
//TODO add photo function - may need to play with tailwind to get this looking good, if not manually style this
//TODO play around with styling via Tailwind more
//"homepage": "/react-nasaphoto",

import './App.css';
import React, { useEffect, useState } from 'react';

import PhotoCard from './PhotoCard';

const today = new Date();

// const nasaApiKey = process.env.REACT_APP_NASA_API;
// const nasa = 'https://api.nasa.gov/planetary/apod';
// const photoDay = nasa + 'planetary/apod';

const App = () => {
  //fetches the photo
  const searchPhotos = async (date) => {
    setPhoto('');
    // const response = await fetch(`${nasa}?date=${date}&api_key=${nasaApiKey}`);
    
    const response = await fetch(`/.netlify/functions/nasaPhotoKey?date=${date}`);
    const data = await response.json();
    //05/10/2010
    // console.log({data})
    setPhoto(data);
  }
  function formatDate4API(date) {
    return date.getFullYear() + '-' + (date.getMonth() + 1 > 9 ? date.getMonth() + 1 : `0${date.getMonth() + 1}`) + '-' + (date.getDate() > 9 ? date.getDate() : '0' + date.getDate());
  }
  function randomDate(){
    let minDate = new Date('1995-06-20').getTime(); //first day with a photo of the day in milliseconds
    const randDate = new Date(Math.random() * (today.getTime() - minDate) + minDate); //makes sure that date is in range
    const formattedDate = formatDate4API(new Date(randDate));
    setDate(formattedDate);
    searchPhotos(formattedDate);
  }
  const [photo, setPhoto] = useState('');
  const todayFormatted = formatDate4API(today);
  const [searchDate, setDate] = useState(todayFormatted);
  useEffect(() => {
    searchPhotos(todayFormatted);
  }, [todayFormatted]); // the ,[] should make sure this runs once on load
  return (
    <div className="App container mx-auto pt-4 bg-white min-h-screen">
     
        <header className='text-center'>
          <h1 className='text-4xl font-semibold'>Nasa Photo of the&nbsp;Day</h1>
        </header>
        <menu className='mt-6 text-center'>
          <label htmlFor="photoDay">
            <p>Pick a date between June 20th, 1995 and today to display the photo for that day</p>
            <p>These photos are being pulled from Nasa's photo of the day API and they have not been optimized for the web. Please be patient as they load.</p>
          </label>
          <input className='border-2 border-indigo-700 text-indigo-700 font-semibold mt-6' type="date" name="photoDay" id="photoDay" min="1995-06-20" max={todayFormatted}
            value={searchDate}
            onChange={(e) => setDate(e.target.value)}
          />
          <button
          className='px-4 py-2 font-semibold text-sm bg-indigo-700 text-white rounded-full shadow-sm ml-6'
            onClick={() => searchPhotos(searchDate)}>Search</button>
          <button
            className='px-4 py-2 font-semibold text-sm bg-indigo-700 text-white rounded-full shadow-sm ml-6'
            onClick={() => randomDate()}
            >Random</button>
        </menu>
        <main>
          <PhotoCard photo={photo} />
        </main>
    </div>
  );
}

export default App;

Here’s what’s in my updated /.netlify/functions/nasaPhotoKey.js

// const { resolvePath } = require("react-router-dom");
// const fetch = require('node-fetch');
// const url = 'https://api.nasa.gov/planetary/apod';
// const apikey = process.env.REACT_APP_NASA_API;

const axios = require('axios');
exports.handler = async (event, context) => {
  // const date = event.queryStringParameters.date;
  // const params = new URLSearchParams([
  //   ['date', `${date}`],
  //   ['api_key', `${apikey}`]
  // ]);
  // const url = 'https://api.nasa.gov/planetary/apod';
  try {
    const response = await axios.get({
      params: {
        apikey: process.env.REACT_APP_NASA_API,
        date: event.queryStringParameters.date
      },
      url: 'https://api.nasa.gov/planetary/apod'
    });
    return {
      statusCode: 200,
      body: JSON.stringify(response.data)
    }
  } catch (err) {
    return {
      statusCode: err.statusCode || 500,
      body: JSON.stringify({
        error: err.message
      })
    }
  }

  //   const params = new URLSearchParams([
  //     ['date', `${date}`],
  //     ['api_key', `${apikay}`]
  // ]);
  //   
  //   try {
  //     const response = await axios.get(url, {params});
  //     return {
  //       statusCode: 200,
  //       body: JSON.stringify(response.data)
  //     }
  //   }
  //   catch(error){
  //     return {
  //       statusCode: 500,
  //       body: JSON.stringify(error)
  //     }
  //   }
};

Hey there, @grifsar :wave:

Thanks so much for your patience here. I want to assure you we have not forgotten about your question and we will follow up soon. Please let us know if anything has changed in the past five days or if you have made any progress.

Thanks for responding. I haven’t made much progress in the past few days. My current plan is to take some time this weekend to try a different tutorial on these sorts of functions and then see if I can figure out how to fix this project.

My current theory about what is wrong with this is that there may be something off with my package.json file or the configuration of something that it’s pulling in. I bounced between a couple of different tutorials early on and there may be something in here that is causing the problem. This is admittedly the most complex thing I’ve attempted to do with Netlify so I could easily be on the wrong track here.

For reference, here’s what’s in my package.json file:

{
  "name": "reactpractice-nasaphoto",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.3.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^0.27.2",
    "http-proxy-middleware": "^2.0.6",
    "netlify-lambda": "^2.0.15",
    "node-fetch": "^3.2.9",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.3.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "api:serve": "netlify-lambda serve .netlify/functions",
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "autoprefixer": "^10.4.7",
    "postcss": "^8.4.14",
    "tailwindcss": "^3.1.4"
  }
}

This could be one problem, though I’d not expect it to be. That package is far from being maintained and not even required.

Feel free to share your repo with me and I can take a look too.

I tried removing that and I’m still getting the same error as before. It sounds like I may have been following an outdated tutorial or something. I also tried adjusting my netlify.toml file to be more in line with a different tutorial that I was looking at, but that seems to have made things worse as I believe it was Axios that threw error about url being an object when it needed a string, and then when I fixed that I got 403 with less info in that what I was getting before. I have since rolled the .toml file back to the way it was before looking at those other tutorials.

Here’s the repo that I’m using for this project in case it helps.

I’ve opened a PR which fixes this:

Oh no, now when I got a notification of my PR being merged, I noticed that I sent an incorrect commit. My bad. I’ll submit another PR.

EDIT:

No worries - I went back and made the changes you suggested to the function and it appears to be working now - thanks!