Skip to content

Security: cool-personal-projects/miamm

Security

SECURITY.md

Important to know how to use API KEYS and understand the ai.js file.


1. Importing Hugging Face SDK

import { HfInference } from '@huggingface/inference'

This imports the Hugging Face Inference client from the package:

@huggingface/inference

This library lets you call AI models hosted on Hugging Face via API.

If this package is not installed, you must run:

npm install @huggingface/inference

2. The system prompt

const SYSTEM_PROMPT = `
You are an assistant that receives a list of ingredients...
`

This is the instruction given to the AI.

It tells the model:

  • it will receive ingredients
  • it must suggest a recipe
  • it can add extra ingredients
  • it must format the response in markdown

Example response from the AI might be:

## Tomato Pasta

### Ingredients
- tomatoes
- pasta
- olive oil
- garlic

Markdown is useful because React can render it nicely.


3. Creating the Hugging Face client

const hf = new HfInference(process.env.HF_ACCESS_TOKEN)

This line is very important.

It creates the Hugging Face client and injects your API key from the environment variable.

Your .env should look like:

HF_ACCESS_TOKEN=your_huggingface_key

And your .gitignore should contain:

.env

So the key never goes to GitHub.


4. The exported function

export async function getRecipeFromMistral(ingredientsArr)

This function:

  • receives an array of ingredients

Example:

["tomato", "onion", "pasta"]

5. Convert array → string

const ingredientsString = ingredientsArr.join(", ")

This transforms:

["tomato", "onion", "pasta"]

into:

"tomato, onion, pasta"

So it can be sent to the AI.


6. Calling the AI model

const response = await hf.chatCompletion({

This sends a request to Hugging Face's API.


Model used

model: "mistralai/Mixtral-8x7B-Instruct-v0.1"

This is the Mixtral model by Mistral AI.

It is a strong instruction-following LLM.


Messages format

messages: [
  { role: "system", content: SYSTEM_PROMPT },
  { role: "user", content: `I have ${ingredientsString}. Please give me a recipe you'd recommend I make!` },
]

This follows the ChatGPT-style format:

System message

Defines the assistant behavior.

User message

Example sent to the model:

I have tomato, onion, pasta. Please give me a recipe you'd recommend I make!

7. Max tokens

max_tokens: 1024

This limits the maximum response size.

1024 tokens ≈ 700–800 words.

Good for recipes.


8. Returning the AI response

return response.choices[0].message.content

The API returns a big JSON object.

Example structure:

response
 └ choices
     └ 0
        └ message
           └ content

You return only the text generated by the AI.


9. Error handling

catch (err) {
  console.error(err.message)
}

If something fails (API error, invalid key, etc.), it logs the error.


10. Will your .env setup work?

It depends on your environment.

Case 1 — Node.js backend ✅

Works perfectly if you add:

import dotenv from "dotenv"
dotenv.config()

Example:

import dotenv from "dotenv"
dotenv.config()

import { HfInference } from '@huggingface/inference'

Case 2 — React frontend ⚠️

If this file runs inside React, it WILL NOT WORK because:

process.env.HF_ACCESS_TOKEN

is not available in the browser.

React requires prefixes:

VITE_HF_ACCESS_TOKEN

or

REACT_APP_HF_ACCESS_TOKEN

But exposing API keys in frontend is dangerous.


11. Important security issue

If this runs in React, your API key will be visible in the browser.

Anyone could steal it.

Best architecture:

React (frontend)
      ↓
Node / Express API
      ↓
HuggingFace API

So the key stays only on the server.


12. Example usage in your app

const recipe = await getRecipeFromMistral([
  "chicken",
  "rice",
  "garlic"
])

console.log(recipe)

AI response example:

## Garlic Chicken Rice

### Ingredients
- chicken
- rice
- garlic
- olive oil

Summary

Your file:

  • correctly calls Hugging Face inference API
  • uses Mixtral
  • formats a recipe assistant
  • reads the API key from .env

It will work if:

  • .env contains HF_ACCESS_TOKEN
  • .env is ignored by git
  • dotenv is loaded (Node.js)
  • @huggingface/inference is installed

Work with Node.js backend

Using Node.js as a backend lets you keep your API keys on the server, so they never reach the browser. The frontend (React) calls your backend, and the backend calls the AI API. This is the standard architecture used in production.


1. Why a backend makes your API key secure

Without a backend:

React App (browser)
      ↓
HuggingFace API

Your key must be inside the React code → anyone can see it in DevTools.

With a backend:

React (frontend)
      ↓ HTTP request
Node.js backend
      ↓
HuggingFace API

Only the Node server knows the API key. The browser never sees it.


2. Basic backend structure

Example project structure:

project
│
├── frontend (React)
│
└── backend
    ├── server.js
    ├── ai.js
    ├── .env
    ├── package.json

Your API key lives only in the backend .env file.


3. Create the Node backend

Inside a new folder:

mkdir backend
cd backend
npm init -y

Install dependencies:

npm install express dotenv cors @huggingface/inference

What these do:

  • express → create API routes
  • dotenv → load .env
  • cors → allow your React app to call the backend
  • huggingface sdk → call the AI

4. Store the API key

Create .env:

HF_ACCESS_TOKEN=your_huggingface_key

Add .env to .gitignore:

.env
node_modules

5. Backend AI logic (ai.js)

import { HfInference } from "@huggingface/inference"

const hf = new HfInference(process.env.HF_ACCESS_TOKEN)

export async function getRecipe(ingredients) {

  const ingredientsString = ingredients.join(", ")

  const response = await hf.chatCompletion({
    model: "mistralai/Mixtral-8x7B-Instruct-v0.1",
    messages: [
      { role: "system", content: "You are a recipe assistant." },
      { role: "user", content: `I have ${ingredientsString}. Suggest a recipe.` }
    ],
    max_tokens: 500
  })

  return response.choices[0].message.content
}

Now the API key stays on the server.


6. Create the API server (server.js)

import express from "express"
import cors from "cors"
import dotenv from "dotenv"
import { getRecipe } from "./ai.js"

dotenv.config()

const app = express()

app.use(cors())
app.use(express.json())

app.post("/recipe", async (req, res) => {

  const ingredients = req.body.ingredients

  try {
    const recipe = await getRecipe(ingredients)
    res.json({ recipe })
  } catch (err) {
    res.status(500).json({ error: "AI request failed" })
  }

})

app.listen(3000, () => {
  console.log("Server running on port 3000")
})

Your backend now exposes:

POST /recipe

7. Call it from React

Instead of calling HuggingFace directly, your React app calls your backend.

Example:

async function getRecipe(ingredients) {

  const response = await fetch("http://localhost:3000/recipe", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ ingredients })
  })

  const data = await response.json()

  return data.recipe
}

Flow now becomes:

React → Node backend → HuggingFace

8. What the browser sees

The browser only sees:

POST http://localhost:3000/recipe

It never sees your HuggingFace key.


9. Run the backend

Start the server:

node server.js

or better:

npm install nodemon
npx nodemon server.js

10. Production deployment

Later you can deploy the backend on:

  • Render
  • Railway
  • Fly.io
  • AWS
  • DigitalOcean

Your .env variables become environment variables on the server.


11. Why this architecture is important

This gives you:

  • 🔒 secure API keys
  • 🚀 scalable backend
  • 📡 reusable API
  • 🧠 real production architecture

Every professional AI SaaS works like this.

There aren't any published security advisories