Function only returns 400s and 500s & does not even attempt to fetch from Planetscale

I created a new React site, mellow-lolly-ecaae8, to learn how to use the PlanetScale integration and also to make my first simple serverless function on Netlify.

I created an account on Planetscale and used it to set up my database, wouldattend-2023. I used Planetscale’s online console to make the schema and insert some rows, which succeeded.

(By the way, why is Planetscale offering me a DATABASE_URL=mysql:// string? I would expect to need this in a .env file, but the Netlify Planetscale documentation doesn’t mention it. Does withPlanetscale just handle it on Netlify behind the scenes?)

My package.json:

  "dependencies": {
    "@netlify/planetscale": "^1.0.0",
    "@planetscale/database": "^1.6.0",
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/react": "^13.0.0",
    "@testing-library/user-event": "^13.2.1",
    "formdata-polyfill": "^4.0.10",
    "mysql2": "^3.2.0",
    "netlify-identity-widget": "^1.9.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "styled-components": "^5.3.9",
    "web-vitals": "^2.1.0"

Here’s my serverless function at functions/getProposals.js:

import { Handler } from "@netlify/functions";
import { withPlanetscale } from "@netlify/planetscale";

export const handler: Handler = withPlanetscale(async (event, context) => {
  const {
    planetscale: { connection },
  } = context;

  const { body } = event;

  if (!body) {
    return {
      statusCode: 400,
      body: "Missing body",

  const proposals = await connection.execute(
    "SELECT title, description FROM Sessions)"

  return {
    statusCode: 200,
    body: JSON.stringify(proposals)

Here’s the component ProposalsListComponent.js where I call it with a fetch:

import React, { useState, useEffect } from 'react';

const ProposalList = () => {
  const [proposals, setProposals] = useState([]);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch('/functions/getProposals', { method: 'GET' });
        console.log("getProposals/response ", response)
      } catch (error) {
        console.error('error: ', error);
        const errorMessage = await error.text();
        console.log('errorMessage ', errorMessage);

  }, []);

  return (
      {proposals?.map((proposal) => (
        <div key={}>

export default ProposalList;

When I run it locally with netlify dev, it hits "http://localhost:8888/.netlify/functions/getProposals" and returns a 404.

When I commit and deploy this on Netlify, the console.log message in the deploy shows
Failed to load resource: the server responded with a status of 400 ()

The Netlify Functions log just says:
Mar 26, 11:17:18 AM: 3b00941e Duration: 4.42 ms Memory Usage: 62 MB Init Duration: 193.92 ms

Why would this be?

Hey Matt!

First off, thank you so much for using the integration.

I think here lies the issue

  const { body } = event;

  if (!body) {
    return {
      statusCode: 400,
      body: "Missing body",

Looking at your fetch, no body is passed (as there shouldn’t for a GET), so the function handler getProposals rejects the request with a 400. If you remove that check for the body, we should be good to go.

const response = await fetch('/functions/getProposals', { method: 'GET' });

The wrapper method withPlanetscale performs no logic internally that can return a response other than your handler (technically it could return a 502 if there is an error thrown, but that doesn’t seem to be happening here).

As for the 404 locally, is the functions folder directory configured correctly in the netlify.toml? I’ve tripped up myself a few times by not having the functions in netlify/functions for example. But odd it’s working on the deployed site.

1 Like

Thanks! There was also a parentheses inside the quote marks in the SQL expression. Debugging is always harder when there are several problems simultaneously.

It works now!

Also, my functions are in the /functions folder, but I never actually had a netlify.toml file. I should rectify that just in case.