How I Built a Modern 'Food Menu' Web App: From Tech Stack to Workflow
Andrew Baisden

Andrew Baisden @andrewbaisden

About: Software Developer | Content Creator | AI, Tech, Programming

Location:
London, UK
Joined:
Feb 11, 2020

How I Built a Modern 'Food Menu' Web App: From Tech Stack to Workflow

Publish Date: Jun 7 '23
99 46

Introduction

Building successful and efficient web apps needs two things: a strong technological foundation, and a solid approach to managing the work itself.

Many development resources focus on one or the other. There are many good technical resources documenting how to build with various technologies and frameworks. There are also many good sources of insight for how to go about managing the development workflow across multiple stakeholders.

But in this article, I will attempt to combine both into one and illustrate how I leveraged some great technologies to not only build a full-stack application, but also to manage the process and the pre-release workflow.

Technologies I used

We'll be looking at how I worked with four main technologies: Node.js, GraphQL, Next.js, and Preevy. Each of these tools has been impactful for the broader web development community, and this is why I thought this was an interesting project and tech stack example to share with the community.

Node.js, for example, provides developers with scalable and efficient backend development tools. Then there's GraphQL, a cutting-edge data query language that's changing the way we handle and modify data. Next.js is a sophisticated React framework that aids in the creation of highly performant and SEO-friendly front-end interfaces. Finally, Preevy is a tool for quickly and easily provisioning pre-release preview environments on your cloud (Amazon, Google or Microsoft). Preevy's shareable preview environments are a useful way for developers to share their latest code changes, get feedback and collaborate with other stakeholders before any code is merged to staging or production, enabling developers to enjoy a faster pre-release review workflow.

Our summary of building a full-stack application will cover the following topics:

  • Building the Node.js Express backend server
  • Building the Next.js frontend server
  • Running the project in Docker
  • Provisioning a shareable preview environment using Preevy

Building a "food menu" app in public

I've written this summary in the style of a "learning in public" diary. Hopefully this will make the content both interesting and accessible to a wide audience.

The app that we will be building will be for a food menu. Essentially there will be a Node.js backend with a GraphQL server that has an endpoint for our data which will be hard coded. And the Next.js frontend will connect to the Node.js backend to retrieve the data using GraphQL queries.

Prerequisites

Make sure you have the prerequisites now let's get started!

Building the Node.js Express backend server

We are going to start by creating our project locally on our computer. Navigate to a directory and run the commands below to setup our project and backend structure:

mkdir menu-project
cd menu-project
mkdir server
touch docker-compose.yml
cd server
npm init -y
npm i express express-graphql graphql cors
npm i -D dotenv nodemon
mkdir data schema
touch .env Dockerfile index.js
touch data/menu.js schema/schema.js
Enter fullscreen mode Exit fullscreen mode

Next, open the project in a code editor and then add the upcoming code to the correct files.

Put this code in data/menu.js:

const bases = [
  {
    id: '1',

    menuItem: 'Base',

    name: 'Egg Noodles',
  },

  {
    id: '2',

    menuItem: 'Base',

    name: 'Whole-wheat Noodles',
  },

  {
    id: '3',

    menuItem: 'Base',

    name: 'Rice Noodles',
  },

  {
    id: '4',

    menuItem: 'Base',

    name: 'Udon Noodles',
  },

  {
    id: '5',

    menuItem: 'Base',

    name: 'Jasmine Rice',
  },

  {
    id: '6',

    menuItem: 'Base',

    name: 'Whole-grain Rice',
  },
];

const vegetables = [
  {
    id: '1',

    menuItem: 'Vegetables',

    name: 'Pak Choi',
  },

  {
    id: '2',

    menuItem: 'Vegetables',

    name: 'Shiitake Mushrooms',
  },

  {
    id: '3',

    menuItem: 'Vegetables',

    name: 'Champignon Mushrooms',
  },

  {
    id: '4',

    menuItem: 'Vegetables',

    name: 'Mixed Peppers',
  },

  {
    id: '5',

    menuItem: 'Vegetables',

    name: 'Broccoli',
  },

  {
    id: '6',

    menuItem: 'Vegetables',

    name: 'Spinach',
  },

  {
    id: '7',

    menuItem: 'Vegetables',

    name: 'Baby Corn',
  },

  {
    id: '8',

    menuItem: 'Vegetables',

    name: 'Red Onion',
  },

  {
    id: '9',

    menuItem: 'Vegetables',

    name: 'Bamboo Shoots',
  },
];

const meats = [
  {
    id: '1',

    menuItem: 'Meat',

    name: 'Chicken',
  },

  {
    id: '2',

    menuItem: 'Meat',

    name: 'Chicken Katsu',
  },

  {
    id: '3',

    menuItem: 'Meat',

    name: 'Beef',
  },

  {
    id: '4',

    menuItem: 'Meat',

    name: 'Pulled Beef',
  },

  {
    id: '5',

    menuItem: 'Meat',

    name: 'Bacon',
  },

  {
    id: '6',

    menuItem: 'Meat',

    name: 'Pork',
  },

  {
    id: '7',

    menuItem: 'Meat',

    name: 'Duck',
  },

  {
    id: '8',

    menuItem: 'Meat',

    name: 'Prawns',
  },

  {
    id: '9',

    menuItem: 'Meat',

    name: 'Tofu',
  },
];

const sauces = [
  {
    id: '1',

    menuItem: 'Sauce',

    name: 'Sweet Teriyaki',
  },

  {
    id: '2',

    menuItem: 'Sauce',

    name: 'Sweet and Sour',
  },

  {
    id: '3',

    menuItem: 'Sauce',

    name: 'Garlic and black pepper',
  },

  {
    id: '4',

    menuItem: 'Sauce',

    name: 'Oyster Sauce',
  },

  {
    id: '5',

    menuItem: 'Sauce',

    name: 'Hot soybean sauce',
  },

  {
    id: '6',

    menuItem: 'Sauce',

    name: 'Yellow curry & coconut',
  },

  {
    id: '7',

    menuItem: 'Sauce',

    name: 'Peanut',
  },

  {
    id: '8',

    menuItem: 'Sauce',

    name: 'Asian spiced red sauce',
  },
];

module.exports = { bases, vegetables, meats, sauces };
Enter fullscreen mode Exit fullscreen mode

This code will go into schema/schema.js:

const { bases, vegetables, meats, sauces } = require('../data/menu');

const {
  GraphQLObjectType,

  GraphQLID,

  GraphQLString,

  GraphQLList,

  GraphQLSchema,
} = require('graphql');

const BaseType = new GraphQLObjectType({
  name: 'Base',

  fields: () => ({
    id: { type: GraphQLID },

    menuItem: { type: GraphQLString },

    name: { type: GraphQLString },
  }),
});

const VegetableType = new GraphQLObjectType({
  name: 'Vegetable',

  fields: () => ({
    id: { type: GraphQLID },

    menuItem: { type: GraphQLString },

    name: { type: GraphQLString },
  }),
});

const MeatType = new GraphQLObjectType({
  name: 'Meat',

  fields: () => ({
    id: { type: GraphQLID },

    menuItem: { type: GraphQLString },

    name: { type: GraphQLString },
  }),
});

const SauceType = new GraphQLObjectType({
  name: 'Sauce',

  fields: () => ({
    id: { type: GraphQLID },

    menuItem: { type: GraphQLString },

    name: { type: GraphQLString },
  }),
});

const RootQuery = new GraphQLObjectType({
  name: 'RootQueryType',

  fields: {
    bases: {
      type: new GraphQLList(BaseType),

      resolve(parent, args) {
        return bases;
      },
    },

    base: {
      type: BaseType,

      args: { id: { type: GraphQLID } },

      resolve(parent, args) {
        return bases.find((base) => base.id === args.id);
      },
    },

    vegetables: {
      type: new GraphQLList(VegetableType),

      resolve(parent, args) {
        return vegetables;
      },
    },

    vegetable: {
      type: VegetableType,

      args: { id: { type: GraphQLID } },

      resolve(parent, args) {
        return vegetables.find((vegetable) => vegetable.id === args.id);
      },
    },

    meats: {
      type: new GraphQLList(MeatType),

      resolve(parent, args) {
        return meats;
      },
    },

    meat: {
      type: MeatType,

      args: { id: { type: GraphQLID } },

      resolve(parent, args) {
        return meats.find((meat) => meat.id === args.id);
      },
    },

    sauces: {
      type: new GraphQLList(SauceType),

      resolve(parent, args) {
        return sauces;
      },
    },

    sauce: {
      type: SauceType,

      args: { id: { type: GraphQLID } },

      resolve(parent, args) {
        return sauces.find((sauce) => sauce.id === args.id);
      },
    },
  },
});

module.exports = new GraphQLSchema({
  query: RootQuery,
});
Enter fullscreen mode Exit fullscreen mode

And here we have our .env file:

NODE_ENV = "development"

PORT = 8080
Enter fullscreen mode Exit fullscreen mode

Let's put this code in the Dockerfile:

FROM node:18

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 8080

CMD ["node", "index.js"]
Enter fullscreen mode Exit fullscreen mode

And our index.js file gets this server code:

const express = require('express');

const cors = require('cors');

require('dotenv').config();

const { graphqlHTTP } = require('express-graphql');

const schema = require('./schema/schema');

const app = express();

app.use(cors());

app.use(
  '/graphql',

  graphqlHTTP({
    schema,

    graphiql: process.env.NODE_ENV === 'development',
  })
);

const port = process.env.PORT || 8080;

app.listen(port, () =>
  console.log(`Server running on port ${port}, http://localhost:${port}`)
);
Enter fullscreen mode Exit fullscreen mode

And lastly the docker-compose.yml file:

version: '3'
services:
  server:
    container_name: server
    build:
      context: ./server
      dockerfile: Dockerfile
    volumes:
      - ./server:/app
    ports:
      - '8080:8080'
    environment:
      - NODE_ENV=development

  client:
    container_name: client
    build:
      context: ./client
      dockerfile: Dockerfile
    volumes:
      - ./client/src:/app/src
      - ./client/public:/app/public
    restart: always
    ports:
      - 3000:3000
Enter fullscreen mode Exit fullscreen mode

We just have to add these run scripts to the package.json file and we are good to go:]

"scripts": {

"start": "node index.js",

"dev": "nodemon index.js"

},
Enter fullscreen mode Exit fullscreen mode

Now with the backend part of our project setup just go into the root folder for the server and run the command below to start the backend server:

npm run start
Enter fullscreen mode Exit fullscreen mode

Just go to http://localhost:8080/graphql to see your GraphQL API.

Next up is the front end let's get to it.

Building the Next.js frontend server

Change your directory so that it is in the root of the menu-project folder and run the commands below to set up our project to use Next.js.

npx create-next-app client
Enter fullscreen mode Exit fullscreen mode

Complete the setup I used this configuration here:

✔ Would you like to use TypeScript with this project? … No / Yes
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use Tailwind CSS with this project? … No / Yes
✔ Would you like to use src/ directory with this project? … No / Yes
✔ Use App Router (recommended)? … No / Yes
✔ Would you like to customize the default import alias? … No / Yes

cd into the client folder and run this command to install the packages we will need:

npm i @apollo/client graphql
Enter fullscreen mode Exit fullscreen mode

We need to create project files now so let's run this code to get them done:

touch Dockerfile
cd src/app
mkdir components queries utils
touch components/Bases.js components/Meats.js components/Sauces.js components/Vegetables.js
touch queries/clientQueries.js
touch utils/withApollo.js
Enter fullscreen mode Exit fullscreen mode

All that's left is to add the code to our files and we are done. So starting with components/Bases.js:

'use client';

import { useQuery } from '@apollo/client';

import { GET_BASE } from '../queries/clientQueries';

import withApollo from '../utils/withApollo';

const Bases = () => {
  const { loading, error, data } = useQuery(GET_BASE);

  if (loading) return <p>Loading bases...</p>;

  if (error) return <p>The food failed to load there is a problem</p>;

  return (
    <div>
      {!loading && !error && (
        <div className="base-box">
          <div>
            <div className="cost-container">
              <h1>01</h1>

              <p>$5.95 only one</p>
            </div>

            <h2>
              Chose <br />
              your base
            </h2>

            {data.bases.map((bases) => (
              <div key={bases.id}>
                <table>
                  <tr>
                    <td>
                      {bases.id} {bases.name}
                    </td>
                  </tr>
                </table>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

export default withApollo(Bases);
Enter fullscreen mode Exit fullscreen mode

Next up is components/Meats.js:

'use client';

import { useQuery } from '@apollo/client';

import { GET_MEAT } from '../queries/clientQueries';

import withApollo from '../utils/withApollo';

const Meats = () => {
  const { loading, error, data } = useQuery(GET_MEAT);

  if (loading) return <p>Loading meats...</p>;

  if (error) return <p>The food failed to load there is a problem</p>;

  return (
    <div>
      {!loading && !error && (
        <div className="meat-box">
          <div>
            <div className="cost-container">
              <h1>03</h1>

              <p>$1.25 each 2 Max</p>
            </div>

            <h2>
              Choose <br /> your Meats
            </h2>

            {data.meats.map((meats) => (
              <div key={meats.id}>
                <table>
                  <tr>
                    <td>
                      {meats.id} {meats.name}
                    </td>
                  </tr>
                </table>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

export default withApollo(Meats);
Enter fullscreen mode Exit fullscreen mode

Following that add this code to components/Sauces.js:

'use client';

import { useQuery } from '@apollo/client';

import { GET_SAUCE } from '../queries/clientQueries';

import withApollo from '../utils/withApollo';

const Sauces = () => {
  const { loading, error, data } = useQuery(GET_SAUCE);

  if (loading) return <p>Loading sauces...</p>;

  if (error) return <p>The food failed to load there is a problem</p>;

  return (
    <div>
      {!loading && !error && (
        <div className="sauces-box">
          <div>
            <div className="cost-container">
              <h1>04</h1>

              <p>FREE</p>
            </div>

            <h2>
              Choose <br />
              your Sauces
            </h2>

            {data.sauces.map((sauces) => (
              <div key={sauces.id}>
                <table>
                  <tr>
                    <td>
                      {sauces.id} {sauces.name}
                    </td>
                  </tr>
                </table>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

export default withApollo(Sauces);
Enter fullscreen mode Exit fullscreen mode

This code will be going into components/Vegetables.js:

'use client';

import { useQuery } from '@apollo/client';

import { GET_VEGETABLE } from '../queries/clientQueries';

import withApollo from '../utils/withApollo';

const Vegetables = () => {
  const { loading, error, data } = useQuery(GET_VEGETABLE);

  if (loading) return <p>Loading vegetables...</p>;

  if (error) return <p>The food failed to load there is a problem</p>;

  return (
    <div>
      {!loading && !error && (
        <div className="vegetable-box">
          <div>
            <div className="cost-container">
              <h1>02</h1>

              <p>$1.25 each 4 Max</p>
            </div>

            <h2>
              Choose <br />
              your Vegetables
            </h2>

            {data.vegetables.map((vegetables) => (
              <div key={vegetables.id}>
                <table>
                  <tr>
                    <td>
                      {vegetables.id} {vegetables.name}
                    </td>
                  </tr>
                </table>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

export default withApollo(Vegetables);
Enter fullscreen mode Exit fullscreen mode

Now over to the queries/clientQueries.js:

import { gql } from '@apollo/client';

const GET_BASE = gql`
  query getBase {
    bases {
      id

      name

      menuItem
    }
  }
`;

const GET_VEGETABLE = gql`
  query getVegetable {
    vegetables {
      id

      name

      menuItem
    }
  }
`;

const GET_MEAT = gql`
  query getMeat {
    meats {
      id

      name

      menuItem
    }
  }
`;

const GET_SAUCE = gql`
  query getSauce {
    sauces {
      id

      name

      menuItem
    }
  }
`;

export { GET_BASE, GET_VEGETABLE, GET_MEAT, GET_SAUCE };
Enter fullscreen mode Exit fullscreen mode

Almost done this code is for utils/withApollo.js:

import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

import { useMemo } from 'react';

export function initializeApollo(initialState = null) {
  const _apolloClient = new ApolloClient({
    // Local GraphQL Endpoint

    uri: 'http://localhost:8080/graphql',

    // Add your Preevy GraphQL Endpoint

    // uri: 'https://your-backend-server-livecycle.run/graphql',

    cache: new InMemoryCache().restore(initialState || {}),
  });

  return _apolloClient;
}

export function useApollo(initialState) {
  const store = useMemo(() => initializeApollo(initialState), [initialState]);

  return store;
}

export default function withApollo(PageComponent) {
  const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
    const client = useApollo(apolloState);

    return (
      <ApolloProvider client={client}>
        <PageComponent {...pageProps} />
      </ApolloProvider>
    );
  };

  // On the server

  if (typeof window === 'undefined') {
    WithApollo.getInitialProps = async (ctx) => {
      const apolloClient = initializeApollo();

      let pageProps = {};

      if (PageComponent.getInitialProps) {
        pageProps = await PageComponent.getInitialProps(ctx);
      }

      if (ctx.res && ctx.res.finished) {
        // When redirecting, the response is finished.

        // No point in continuing to render

        return pageProps;
      }

      const apolloState = apolloClient.cache.extract();

      return {
        ...pageProps,

        apolloState,
      };
    };
  }

  return WithApollo;
}
Enter fullscreen mode Exit fullscreen mode

Next up our CSS in globals.css so replace all the code with this one:

*,
*::before,
*::after {
  margin: 0;

  padding: 0;

  box-sizing: border-box;
}

html {
  font-size: 16px;
}

header h1 {
  text-align: center;

  color: #ffffff;

  font-size: 4rem;

  text-transform: uppercase;
}

h1 {
  color: #1c1917;
}

h2 {
  color: #1c1917;

  font-size: 1.4rem;

  text-transform: uppercase;

  border-top: 0.3rem solid black;

  border-bottom: 0.3rem solid black;

  margin-bottom: 1rem;

  padding: 1rem 0 1rem 0;
}

body {
  background: #374151;
}

.container {
  width: 100%;

  max-width: 90rem;

  margin: 2rem auto;

  display: flex;

  flex-flow: row wrap;

  justify-content: space-around;

  background: #f9fafb;

  padding: 2rem;
}

.base-box,
.vegetable-box,
.meat-box,
.sauces-box {
  background: #f43f5e;

  width: 20rem;

  height: auto;

  padding: 1rem;

  color: #ffffff;

  font-weight: bold;

  margin-bottom: 2rem;
}

.cost-container {
  display: flex;

  flex-flow: row nowrap;

  justify-content: space-between;

  align-items: center;

  margin-bottom: 1rem;
}

.cost-container p {
  background: #eff6ff;

  padding: 0.2rem;

  color: #f43f5e;

  font-size: 1rem;
}

@media screen and (max-width: 800px) {
  .container {
    flex-flow: column;

    align-items: center;
  }
}
Enter fullscreen mode Exit fullscreen mode

Just one more to go after this, so next is the page.js file and like before replace all the code with what we have here:

'use client';

import Bases from './components/Bases';

import Vegetables from './components/Vegetables';

import Meats from './components/Meats';

import Sauces from './components/Sauces';

export default function Home() {
  return (
    <>
      <header>
        <h1>Menu</h1>
      </header>

      <div className="container">
        <Bases />

        <Vegetables />

        <Meats />

        <Sauces />
      </div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Finally, let's complete our project by putting this code in the Dockerfile in the client folder:

FROM node:18-alpine

WORKDIR /app

# Install dependencies based on the preferred package manager

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./

RUN \

if [ -f yarn.lock ]; then yarn --frozen-lockfile; \

elif [ -f package-lock.json ]; then npm ci; \

elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \

# Allow install without lockfile, so example works even without Node.js installed locally

else echo "Warning: Lockfile not found. It is recommended to commit lockfiles to version control." && yarn install; \

fi

COPY src ./src

COPY public ./public

COPY next.config.mjs .

# Next.js collects completely anonymous telemetry data about general usage. Learn more here: https://nextjs.org/telemetry

# Uncomment the following line to disable telemetry at run time

# ENV NEXT_TELEMETRY_DISABLED 1

# Note: Don't expose ports here, Compose will handle that for us

# Start Next.js in development mode based on the preferred package manager

CMD \

if [ -f yarn.lock ]; then yarn dev; \

elif [ -f package-lock.json ]; then npm run dev; \

elif [ -f pnpm-lock.yaml ]; then pnpm dev; \

else yarn dev; \

fi
Enter fullscreen mode Exit fullscreen mode

Now with the frontend and backend side of our project setup just go into the root folder for the client and run the command below to start the frontend server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Double-check that your backend server is still running and you should see the menu which is receiving data from the backend server.

The next section will show us how to run the app inside of a Docker container so on we go.

Running the project in Docker

Getting your application to run in Docker is super easy. First, make sure that Docker is running on your computer and that the other servers are not running because they will use the same ports. Then just ensure that you are inside the root folder for menu-project and then run the command docker-compose up. This will run the backend and frontend servers at the same time. Now just got to the same URLs as before for the server and client to see everything working.

The server is running on http://localhost:8080
The client is running on http://localhost:3000

Lastly, its time to deploy a preview environment using Preevy. By doing this we can share our work very easily with other people working on the project. They'll be able to see the latest version of the app with a single click, without needing to see how it looks or behaves in our development environment. This has already saved me hours of back and forth and I'm very happy to have found this tool and included it in my workflow.

Creating a provision preview environment on Preevy

First, read the documentation on Preevy and install it and then get ready to run the commands here to create a provisioning environment. Inside the root folder for menu-project run the following commands:

preevy init
preevy up --id 321
Enter fullscreen mode Exit fullscreen mode

You must pass an --id flag with a string of numbers. I used 321 as an example which should work. The setup might take some time to complete because it has to create the backend and the front end on AWS. When it's complete you should get two URL's one for the server and one for the client see the example here:

server  8080 https://your-url-server.livecycle.run/
client  3000 https://your-url-client.livecycle.run/
Enter fullscreen mode Exit fullscreen mode

Clicking on the links should take you to both servers running online. You will notice that the page is showing errors and not loading our data on the client link. That's because it's still set to http://localhost:8080/graphql in your code. Update the uri in the file client/src/app/utils/withApollo.js to your Preevy server GraphQL endpoint and then run the command preevy up --id 321 again to push the latest changes.

Now the page should be showing data from our GraphQL API.

Conclusion

Ultimately, the combination of tools like Node.js, GraphQL, Next.js is transforming the online development environment by bringing improved performance, flexibility, and data privacy compliance to the forefront. When these technologies are combined, they constitute a strong toolbox that enables developers to create scalable, efficient, and privacy-conscious apps.

And tools like Preevy facilitate an only better developer experience by enabling developers lightning fast ways to securely deploy and share their work with others, so they can collect clear feedback and release higher-quality products.

Hopefully, you've found that this article presents a realistic strategy for exploiting the synergy of these technologies.

However, because technology is always evolving, it is obligatory for every developer to constantly research and experiment with these tools. With this knowledge, you'll be well-equipped to take your web development talents to the next level, keeping on the cutting edge of technical innovation and creating apps that truly make a difference.

Comments 46 total

  • Andrei Sibisan
    Andrei SibisanJun 8, 2023

    4 techs for a food menu... seems a little over engineered...

    • Clarance Farley
      Clarance FarleyJun 13, 2023

      Nice one @andreisibisan

    • Andrew Baisden
      Andrew BaisdenJun 21, 2023

      It is supposed to be a simple example that shows how easy it is to deploy an application to Preevy 🙂 I was showing how you can use different stacks and get them to work without too much effort. So with that knowledge in mind anyone can deploy their applications whether it is simple or complex.

      • Andrei Sibisan
        Andrei SibisanJun 21, 2023

        yes, I meant no offence and your effort is very commendable, it was more of a general remark on ever increasing complexity and general overhead used in IT. Example: I was shocked to see how nonsensically complex was to make the 'Todo app' in React... I could do it faster and cleaner just with vanilla JS...

        all the boilerplate and overhead makes sense when you truly make something complex and sprawling...

  • Rich Winter
    Rich WinterJun 8, 2023

    I certainly appreciate the effort but honestly, this isn't an Application. While there are database calls, generally speaking you could have written this entire thing with plain vanilla HTML and CSS. I was expecting this to take orders/payment and pass them on to another client or something that makes this an application, not a website.

    • Andrew Baisden
      Andrew BaisdenJun 21, 2023

      I used a simple example so that people could see how easy it is to deloy thier applications to Preevy 🙂 So now if someone wanted to create a complex application as you described then you can see that it wont require that much effort. Basically if you can build it locally then it will work on Preevy.

  • Alejandro LR
    Alejandro LRJun 9, 2023

    Nicely done!!

  • codescandy
    codescandyJun 9, 2023

    Awesome,

  • CodeWorldIndustries
    CodeWorldIndustriesJun 9, 2023

    This is rather disappointing... Also, I would not consider this an app.

    • Andrew Baisden
      Andrew BaisdenJun 21, 2023

      I just needed an example to showcase how easy it is to put your codebase on Preevy. It can be as basic or as complex as you want it to be 😁

      • Jared Thomas
        Jared ThomasFeb 16, 2024

        I appreciated the author's clear and concise writing style. The way they articulated their journey of creating a modern food menu web app was both engaging and informative. Each step of the process was explained in detail, making it easy for readers to follow along regardless of their level of expertise in web development.It is really amazing like chick-fil-a menu breakfast

  • Agus Arias
    Agus AriasJun 12, 2023

    I guess people complaining on the use case didn't get the point of showcasing the interaction between the tools. Kudos for putting this together.

    • Andrew Baisden
      Andrew BaisdenJun 21, 2023

      Yeah thats it you understood and thanks 🙂

  • Bhavya Sura
    Bhavya SuraJun 13, 2023

    Good Content and Great Effort
    Can you also share the screen shots?

  • hassan
    hassanFeb 29, 2024

    Hello @andrewbaisden thanks for the nice tutorial.

    1) Having this issues while trying to deploy on a prevision environment : CredentialsProviderError: Could not load credentials from any providers

    2) There's a mistake in the Dockerfile image (line 27). You need to change the "COPY next.config.js" to "COPY next.config.mjs ." otherwise you'll have this error in your console : "failed to solve: failed to compute cache key: failed to calculate checksum of ref 6e54fd9d-94d4-4570-8388-b6d0a9998d0b::t9jl1swrd0b4vzg"

    Thanks for sharing

    • Andrew Baisden
      Andrew BaisdenMar 4, 2024

      Thanks @hassanndam I fixed the error. I'm pretty sure it was working before I think Vercel updated Next.js and changed the file type unless I'm mistaken.

  • hamza
    hamza Jun 2, 2024

    Great tutorial! Your creativity in combining Node.js, GraphQL, Next.js, and Preevy is impressive. It's inspiring to see how you built a full-stack food menu app from scratch. Speaking of food menus, i also tried on my project and the result looking nice check out and is it going nice or still have some space for corrections menuspricesphl.com/

  • Saqib Syed
    Saqib SyedJul 13, 2024

    I recently explored a guide on building a 'Food Menu' web app using Node.js, GraphQL, Next.js, and Docker. The instructions on check out were clear, detailed, and easy to follow, making complex topics accessible. The practical approach and integration of modern technologies made the experience both educational and inspiring. Highly recommended for anyone looking to learn modern web development techniques!

  • jollimenupriceph
    jollimenupricephAug 12, 2024

    Thanks for sharing this tutorial! I'm also working on a food menu project but with a focus on all resturant menu pricing in the Philippines. I've created a mobile app, but I'm still ironing out some technical issues. Would love to learn from your experience and get feedback on my project once it's up and running smoothly!

  • dunkindonutsprices
    dunkindonutspricesAug 12, 2024

    It is informative and explained in very easy steps. I am making a menu app for my menu pricing site, but I am facing many challenges regarding nodejs.

  • HMQBL
    HMQBLAug 22, 2024

    @andreisibisan Thanks for detailed overview.

    • kanewilliamson
      kanewilliamsonJan 19, 2025

      If you’re searching for the perfect video editing app, here are some top picks to explore. CapCut stands out with its beginner-friendly interface and versatile editing tools, making it a favorite among mobile users. For those who prefer sleek and professional edits, Adobe Premiere Rush is a fantastic choice. InShot is great for quick, social-media-ready videos, while KineMaster allows multi-layer editing with precision. Don’t forget thecapguru, an emerging platform that offers tips and tricks to get the most out of these apps. Whether you’re a newbie or a pro, these tools make video editing accessible and fun!

  • Murphy
    MurphyAug 29, 2024

    recently I am download the youtube biru apk on my android phone. But i am not able to watch any food related on this app. can anyone tell me why? like this youtubebiru.site/

  • Junaid Akbar
    Junaid AkbarSep 7, 2024

    You explained very well that how you built a Modern Food Menu. Recently I also visit Subway Menu Website and was amazed to see the Menu style on that site. It was Designed in a modern Way and I loved it completely.

  • Lion Life
    Lion LifeOct 26, 2024

    The recommended dosage and frequency depend on the individual's condition and doctor’s advice. Torsilax is generally taken with water after meals to minimize stomach upset due to diclofenac's NSAID properties.

  • Wateen Speed
    Wateen SpeedNov 6, 2024

    A deep dive into a celebrity's biography often reveals the untold story behind their success. It’s not just the fame that makes them special, but their journey to the top.

  • Attiq Mughal
    Attiq MughalNov 11, 2024

    If you’re a fan of seafood, Cheddar’s Scratch Menu has some solid choices. Their Grilled Whitefish is light, perfectly seasoned, and a great option if you’re not in the mood for anything too heavy. Served with rice and fresh vegetables, it’s a nice balance of flavor and freshness. The fish was cooked just right, and it’s awesome to find healthy, tasty options at a place like this

  • gb sheet
    gb sheetDec 20, 2024

    Yeah that's great you explain and thanks 🙂 I am watching how you can use different stacks and get them to work without too much effort.

  • harshit rana
    harshit ranaJan 1, 2025

    Worth reading. I am developing an app for my Mcdonalds menu price list website. But I was facing some issues related to customization. But after reading this detailed blog, all my issues got resolved. Keep sharing such stuff for the betterment of community.

  • John William
    John WilliamJan 5, 2025

    The food menu offers a variety of delectable options, catering to different tastes and preferences. You may find a range of dishes, from light snacks to hearty meals, ensuring something for everyone. Whether you're craving savory bites or sweet indulgences, there's a possibility of something exciting waiting for you. For dessert lovers, an Oreo sundae could be a delightful treat, with flavors that may include a creamy blend of chocolate, vanilla, and crushed Oreos. If you're in the mood for a cold treat, the Oreo sundae Mixue harga could be worth exploring, depending on where you're looking to satisfy your sweet tooth.

  • William Thomas
    William ThomasJan 14, 2025

    Covering the Panda Express menu with prices, nutrition, and 2025 deals is both trendy and informative. This blog will help readers explore Chinese-American food options easily, and the nutrition calculator is a unique and helpful addition. Go here

  • Saim Anderson
    Saim AndersonJan 15, 2025

    Thanks for sharing this valuable add on this amazing food menu web creation topic, I am working on this category from past few years, can you please help me by checking error on my food blog creation of this website subwaymenupoint.uk/.

  • gb sheet
    gb sheetJan 24, 2025

    Hi Andrew,

    Building a modern "Food Menu" web app! 🍴 The way you broke down the tech stack—Node.js, GraphQL, Next.js, and Preevy—was super clear and inspiring. Your workflow insights are helpful for devs at all levels. Thanks for sharing such a great resource! 🙌

  • David
    DavidFeb 26, 2025

    If you're looking for a great restaurant, visit our restaurant's website Visit Cheddar's menu

  • dalsi44
    dalsi44Mar 11, 2025

    Great post! I love how you combined Node.js and GraphQL—this is exactly the setup I’ve been exploring for my own food pricing site. In fact, I’m putting together a page on dunkin donuts menu prices and found your approach with Docker really helpful for keeping everything organized.

    • mcdmenus
      mcdmenusMar 11, 2025

      Thank you so much for the kind words! I’m glad the Node.js and GraphQL setup was helpful for your project—Docker definitely makes everything run smoother, especially when juggling multiple services. If you’re expanding your site, you might also find my latest piece on dunkin donuts box of joe handy for managing menu data in a containerized environment. Keep up the great work on your food pricing site!

  • Hailey Skarke
    Hailey SkarkeMar 25, 2025

    This post had an excellent layout of hungry lion prices! The well-organized format allowed me to grasp the topic quickly without feeling overwhelmed.

    • Sila Ehsan
      Sila EhsanMar 28, 2025

      If you're the fan of Sonic Menu then you must be aware of the latest prices of Sonic burgers and other items. We have covered all the the items in this website. You can check and select your favorite menu items. I hope you will love the taste and delicious items at sonic

  • John
    JohnMar 29, 2025

    Whataburger never disappoints! 🍔🔥 Their menu just keeps getting better, and I love how they always have something new to try. If you’re looking for the latest menu prices to plan your next meal, I found a great resource here. What’s your favorite item to order at Whataburger?

  • ahmad raza
    ahmad razaApr 21, 2025

    Great insight into building a modern web app with a solid tech stack! The combination of Node.js, GraphQL, Next.js, and Preevy sounds like a powerful approach for any full-stack application. I’ve been learning a lot about web development myself and have a website focused on the Dunkin Donuts menu. Do you think this tech stack would work for a site like mine? I'd love to incorporate these technologies for a better user experience and quicker pre-release workflows!

  • Albert john
    Albert johnJun 20, 2025

    Great breakdown really appreciate how you integrated Preevy into the workflow. The shareable preview environments sound like a game-changer for early feedback loops 7 Brew menu.

  • Lena Marie
    Lena MarieJun 28, 2025

    Hey,
    Building a modern food menu web app gives users a smooth and interactive experience, and Mission BBQ does the same by offering a clear, user-friendly layout with strong values and great food. Their focus on design, flavor, and purpose sets a great example for food-focused platforms. Explore the full mission bbq menu to see how well it’s done.

  • Kerstin Iris
    Kerstin IrisJun 29, 2025

    Discover how we built a modern ‘Food Menu’ web app to help users explore Chipotle menu with price easily. From tech stack to design, we cover proteins, veggies, prices, and calories making it simple to pick your favorite burrito, bowl, or quesadilla

  • Kerstin Iris
    Kerstin IrisJun 29, 2025

    Discover how we built a modern ‘Food Menu’ web app to help users explore Chipotle’s full menu easily. From tech stack to design, we cover proteins, veggies, prices, and calories—making it simple to pick your favorite burrito, bowl, or quesadilla

  • Saim Anderson
    Saim AndersonJun 29, 2025

    Great insight into building a modern web app with a solid tech stack! The combination of Node.js, GraphQL, and Next.js sounds like a powerful combo. I’ve been working on a site focused on the Crumbl Cookies menu and I’ve been wondering if adopting a setup like this could improve load speed and flavor update workflows. Would love to hear your thoughts![]

Add comment