Custom customer account page in Shopify custom app

Topic summary

A beginner Shopify developer is building a custom app using Shopify CLI v3.46.0 with Node.js and needs to create a custom customer account page. They’ve completed the basic tutorial but face three key challenges:

Main Questions:

  1. App Architecture: Should they use a classic custom app or an extension within a custom app for a single-store implementation?

  2. Testing Environment: How to test customer-facing functionality when npm run dev only shows the store owner view. Unclear whether ngrok is needed for customer account testing.

  3. Customer Authentication: Struggling to retrieve the logged-in customer’s ID dynamically via GraphQL Admin API. Currently hardcoding customer IDs works in testing, but they need to automatically identify authenticated customers.

Code Attempt:
The developer shared their index.js implementation showing an API endpoint (/api/customer) that attempts to query customer data using GraphQL. The code includes a hardcoded customer ID (gid://shopify/Customer/6913227350594) and questions where the actual customer session ID should be retrieved from.

The discussion remains open with no responses yet, awaiting guidance on proper customer authentication patterns and testing workflows for custom Shopify apps.

Summarized with AI on November 19. AI used: claude-sonnet-4-5-20250929.

Hello there.

I’m beginner in Shopify and I need to develop a small custom app using Shopify CLI v3.46.0 with Node js (Shopify App Template - Node). I went through all the points of the tutorial https://shopify.dev/docs/apps/getting-started/create and now I need to implement my version of the customer personal account for the store. And I have a few questions.

  1. Which custom app option should be used for my purposes - a classic custom app or an extention in a custom app? I’m going to use a custom app for only one my store.
  2. What should I do on the side of the test store or settings of the installed custom app to test the functionality of the customer personal account? I just run npm run dev and go to the hot-reload page, where I see the application as a store owner, not a customer. Should I use ngrok?
  3. I tried to figure out how to prepare a query for the GraphQL Admin API myself, but I got stuck. How do I get the ID of the logged in customer? Where is it stored? If I hardcode the customerID, I will successfully get his data in the test environment. But how do this automaticly for authorized customer? Here is what I tried to do (all changes placed in one file index.js in web folder):
// -check
import { join } from "path";
import { readFileSync } from "fs";
import express from "express";
import serveStatic from "serve-static";

import shopify from "./shopify.js";
import productCreator from "./product-creator.js";
import GDPRWebhookHandlers from "./gdpr.js";

import '@shopify/shopify-api/adapters/node';
import {shopifyApi, LATEST_API_VERSION} from '@shopify/shopify-api';

// Set up shopifyApi for GraphQL
const shopifyGQL = shopifyApi({
  apiKey: process.env.SHOPIFY_API_KEY,
  apiSecretKey: process.env.SHOPIFY_API_SECRET_KEY,
  scopes: ['read_customers,write_customers'],
  hostName: 'ngrok-tunnel-address', //should we use this?
});

const PORT = parseInt(
  process.env.BACKEND_PORT || process.env.PORT || "3000",
  10
);

const STATIC_PATH =
  process.env.NODE_ENV === "production"
    ? `${process.cwd()}/frontend/dist`
    : `${process.cwd()}/frontend/`;

const app = express();

// Set up Shopify authentication and webhook handling
app.get(shopify.config.auth.path, shopify.auth.begin());
app.get(
  shopify.config.auth.callbackPath,
  shopify.auth.callback(),
  shopify.redirectToShopifyOrAppRoot()
);
app.post(
  shopify.config.webhooks.path,
  shopify.processWebhooks({ webhookHandlers: GDPRWebhookHandlers })
);

// If you are adding routes outside of the /api path, remember to
// also add a proxy rule for them in web/frontend/vite.config.js

app.use("/api/*", shopify.validateAuthenticatedSession());

app.use(express.json());

app.get("/api/products/count", async (_req, res) => {
  const countData = await shopify.api.rest.Product.count({
    session: res.locals.shopify.session,
  });
  res.status(200).send(countData);
});

app.get("/api/products/create", async (_req, res) => {
  let status = 200;
  let error = null;

  try {
    await productCreator(res.locals.shopify.session);
  } catch (e) {
    console.log(`Failed to process products/create: ${e.message}`);
    status = 500;
    error = e.message;
  }
  res.status(status).send({ success: status === 200, error });
});

// This is my new functionality about customer info
app.get("/api/customer", async (req, res) => {
  try {
    const session = res.locals.shopify.session;
    const customerId = session.id; // customer id is here?
    const client = new shopifyGQL.clients.Graphql({ session });

    console.log('customerId: ', customerId);

    const response = await client.query({
      data: {
        query: `
          query($customerId: ID!) {
            customer(id: $customerId) {
              email
              firstName
              lastName
            }
          }
        `,
        variables: {
          customerId: "gid://shopify/Customer/6912233570594",
        },
      },
    });

    const customer = response.body.data.customer;
    res.json(customer);
  } catch (error) {
    console.error("Error retrieving customer details:", error);
    res.status(500).json({ error: "Failed to retrieve customer details" });
  }
});

app.use(shopify.cspHeaders());
app.use(serveStatic(STATIC_PATH, { index: false }));

app.use("/*", shopify.ensureInstalledOnShop(), async (_req, res, _next) => {
  return res
    .status(200)
    .set("Content-Type", "text/html")
    .send(readFileSync(join(STATIC_PATH, "index.html")));
});

app.listen(PORT);

And here is how it looks in the component (just small example):

import React from 'react';
import { LegacyCard, TextStyle } from '@shopify/polaris';
import {useAppQuery} from '../hooks/index.js';

export function ProfileView () {
	const {
		data,
		isLoading,
		isSuccess,
		isError,
	} = useAppQuery({
		url: "api/customer",
	});

	return (
		
	);
}