Use Google Maps API with Caching in Golang and React

published on 06 August 2022

Some months ago, I started with a new side-project named ProdutoMania

This project's main feature is finding products from my home country in my current location.
So I need a selector of location and to use Google Place/Maps API is on hand.

dmyxfsk83b9z4d7s7cf5-lymzr

This API is paid as soon as usage gets above a certain amount.

This can be risky if you put the queries as autocomplete/typehead directly fetching results on the client-side. So I decided to call the backend API with a search term and the handler on the server-side took control of the usage of the API.

This opens the possibility of doing throttling (not part of this post) and caching. Caching makes much sense, as locations do not change every minute, hour, not even day.
There is a max period allowed by Google for caching; at the time of writing this post, it was 30 days.

To build the API I use:

  • Golang with Chi Router (could also be done without, just using net/http standard package)
  • Google’s official Go Client for Google Maps
  • Caching Middleware victorspringer/http-cache

Victor Springer’s Caching Middleware is a perfect fit to cache RESTful APIs. It supports memory, Redis, DynamoDB, and other storage for the cache.

here is the config part of the story:

// Cache Middleware Config
memcached, err := memory.NewAdapter(
    memory.AdapterWithAlgorithm(memory.LRU),
    memory.AdapterWithCapacity(1000000),
)
if err != nil {
    fmt.Println(err.Error())
    os.Exit(1)
}
cacheClient, err := cache.NewClient(
    cache.ClientWithAdapter(memcached),
    cache.ClientWithTTL(24 * time.Hour),
    cache.ClientWithRefreshKey("opn"),
)

Then I define the handler and routes applying the middleware:

// Cache Google Place API calls
hLocation := http.HandlerFunc(handler.GetLocations)

r.Route("/", func(r chi.Router) {

// location autocomplete
r.With().Get("/{term}", CacheClient.Middleware(hLocation).ServeHTTP)
})

On Frontend Side I use:

The Debouncing is important for the typeahead function to not call API on every onChange triggered by a Char. It also does not make sense to send just one char to API.

here is the useLocation Hook/Service part of the code:

export function useLocation() {
  const [{ isLoading, error, data }, dispatch] = useReducer(
    reducer,
    initialState
  )
  const fetchLocationResults = debounce(
    async (searchString) => {
      if (searchString.length > 2) {
        const locationUrl = `http://localhost:9090/${searchString}`
        dispatch({ type: actionTypes.FETCH_REQUEST })
        try {
          const response = await axios.get(locationUrl)
          dispatch({
            type: actionTypes.FETCH_SUCCESS,
            results: response.data,
          })
        } catch (error) {
          dispatch({ type: actionTypes.FETCH_FAILURE, error })
        }
      }
    },
    { wait: 400 }
  )
  return { error, isLoading, data, fetchLocationResults }

You can get the full source to check the details:
https://github.com/stefanwuthrich/cached-google-places

Have fun (without a high invoice from Google)

Read more