How can I test API endpoints with environment variables with MSW

61 Views Asked by At

I want to test marvel api endpoints that needs environment variables in Next.js but I get this error:

Error: ❌ Attempted to access a server-side environment variable on the client.

getHash src/services/api.ts:7:23
  5| const getTimestamp = () => Date.now().toString();
  6| const getHash = (timeStamp: string) =>
  7|   md5(timeStamp + env.MARVEL_API_PRIVATE_KEY + env.MARVEL_API_PUBLIC_KEY);

This is my env file:

import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';

export const env = createEnv({
  server: {
    NODE_ENV: z.enum(['development', 'test', 'production']),
    MARVEL_API_PRIVATE_KEY: z.string(),
    MARVEL_API_PUBLIC_KEY: z.string(),
    MARVEL_API_URL: z.string().url(),
    PORT: z.coerce.number().default(3000),
  },

  client: {},

  runtimeEnv: {
    NODE_ENV: process.env.NODE_ENV,
    MARVEL_API_URL: process.env.MARVEL_API_URL,
    MARVEL_API_PUBLIC_KEY: process.env.MARVEL_API_PUBLIC_KEY,
    MARVEL_API_PRIVATE_KEY: process.env.MARVEL_API_PRIVATE_KEY,
    PORT: process.env.PORT,
  },
});

The service api function:

import md5 from 'md5';

import { env } from '@/env';

const getTimestamp = () => Date.now().toString();
const getHash = (timeStamp: string) =>
  md5(timeStamp + env.MARVEL_API_PRIVATE_KEY + env.MARVEL_API_PUBLIC_KEY);

const timeStamp = getTimestamp();
const hash = getHash(timeStamp);
const query = `ts=${timeStamp}&apikey=${env.MARVEL_API_PUBLIC_KEY}&hash=${hash}`;

export const getCharacters = async () => {
  const url = `${env.MARVEL_API_URL}/characters?limit=50&${query}`;
  const response = await fetch(url);
  const data = response.json();

  return data;
};

The component where this function is called:

import { CharacterCard } from '@/app/components/Characters';
import { env } from '@/env';
import { Character } from '@/models/character';
import { getCharacters } from '@/services/api';

import styles from './styles.module.css';

export const CharactersList = async () => {
  const characters = await getCharacters();

  console.log(
    'ENV',
    env.NODE_ENV,
    env.MARVEL_API_URL,
    env.MARVEL_API_PRIVATE_KEY,
    env.MARVEL_API_PUBLIC_KEY,
  );

  return (
    <section className={styles.charactersList}>
      {characters.data.results.map((character: Character) => (
        <CharacterCard key={character.id} character={character} />
      ))}
    </section>
  );
};

While running the application in localhost the env variables are printed in the console but don't understand why not in the testing api call.

And the msw handler:

import { HttpResponse, http } from 'msw';

export const handlers = [
  http.get('/characters', () => {
    return HttpResponse.json(
      {
        id: 1011334,
        name: '3-D Man',
      },
      { status: 200 },
    );
  }),
];
1

There are 1 best solutions below

0
Matthias On

You've not posted what is setup in your @/env dir, but at a guess I'd say you're not loading them properly.

You would typically have, and NextJS supports, having a .env file or .env.local in the root of your project (so not in any special dir). This is then automatically loaded into process.env for you to import.

So, you'd likely need to remove what you've got in your @/env dir (or just make it a .env file in the root of the project).

And then set it up:

KEY=VALUE
KEY2=VALUE2

And then you would reference them like:

console.log(process.env.KEY)

Additionally, and it may not be relevant here. But in NextJS to expose an env var publically, you need to prepend NEXT_PUBLIC so if you did want to use MARVEL_API_PUBLIC_KEY client side you would need to make it NEXT_PUBLIC_MARVEL_API_PUBLIC_KEY

NextJS has docs here, including some useful stuff about referencing other vars inside your .env* files