shoplazza-app-express
v0.2.4
Published
Express helpers for building Shoplazza apps with OAuth and Admin API support.
Readme
shoplazza-app-express
shoplazza-app-express is an Express integration layer for Shoplazza apps.
It is modeled after @shopify/shopify-app-express, but uses:
The package gives you a shoplazzaApp() factory with:
- OAuth install and callback middleware
- Session storage abstraction with an in-memory default
- Authenticated-session middleware for protected routes
- Embedded-app friendly redirect helpers
- A typed helper to build Shoplazza Admin API clients from the active session
Install
npm install shoplazza-app-expressIf you want to use Redis-backed session storage, also install:
npm install redisQuick start
import express from "express";
import { shoplazzaApp } from "shoplazza-app-express";
const app = express();
const shoplazza = shoplazzaApp({
api: {
clientId: process.env.SHOPLAZZA_CLIENT_ID!,
clientSecret: process.env.SHOPLAZZA_CLIENT_SECRET!,
scopes: ["read_shop", "read_products"],
appUrl: "https://your-app.example.com",
apiVersion: "2025-06",
isEmbeddedApp: true
},
auth: {
path: "/api/auth",
callbackPath: "/api/auth/callback"
},
hooks: {
async afterAuth({ session, admin }) {
console.log(`OAuth completed for ${session.shop}`);
// Run one-time setup work here, for example:
// await registerWebhooks(session);
// await admin.getShop();
}
},
webhooks: {
path: "/api/webhooks"
}
});
app.get(shoplazza.config.auth.path, shoplazza.auth.begin());
app.get(
shoplazza.config.auth.callbackPath,
shoplazza.auth.callback(),
shoplazza.redirectToShoplazzaOrAppRoot()
);
app.post(
shoplazza.config.webhooks!.path,
shoplazza.processWebhooks({
webhookHandlers: {
"app/uninstalled": async ({ shop }) => {
console.log(`App uninstalled from ${shop}`);
}
}
})
);
app.use("/api/*", shoplazza.validateAuthenticatedSession());
app.get("/api/shop", async (_req, res) => {
const shop = await res.locals.shoplazza.admin.getShop();
res.json(shop.data);
});
app.get("/", shoplazza.ensureInstalledOnShop(), (_req, res) => {
res.send("App is installed");
});
app.listen(3000);Redis session storage
import express from "express";
import { createClient } from "redis";
import { RedisOAuthStateStorage, RedisSessionStorage, shoplazzaApp } from "shoplazza-app-express";
const redis = createClient({
url: process.env.REDIS_URL
});
await redis.connect();
const app = express();
const shoplazza = shoplazzaApp({
api: {
clientId: process.env.SHOPLAZZA_CLIENT_ID!,
clientSecret: process.env.SHOPLAZZA_CLIENT_SECRET!,
scopes: ["read_shop"],
appUrl: "https://your-app.example.com"
},
auth: {
path: "/api/auth",
callbackPath: "/api/auth/callback"
},
sessionStorage: new RedisSessionStorage({
client: redis,
prefix: "shoplazza:session",
ttlSeconds: 60 * 60 * 24 * 30
}),
oauthStateStorage: new RedisOAuthStateStorage({
client: redis,
prefix: "shoplazza:oauth-state"
})
});Notes
- Default session storage is memory-only and is not suitable for multi-instance production deployments.
- OAuth
statestorage also defaults to in-memory. For embedded or App Store installs, prefer a shared store such asRedisOAuthStateStorageso callback validation does not depend on third-party cookies or a single app instance. RedisSessionStorageimplements the same storage contract asMemorySessionStorage, but persists sessions in Redis instead of process memory.- The package intentionally mirrors the flow of
@shopify/shopify-app-express, but it does not attempt to emulate Shopify-only features such as App Bridge reauthorization headers. - Start installations from your app's auth route such as
/api/auth?shop=demo-shop.myshoplaza.com, not from a prebuilt Shoplazza/admin/oauth/authorizeor plugin authorization URL. This package stores the OAuthstatein a signed cookie before redirecting to Shoplazza, so skipping the app route will causeInvalid OAuth stateon the callback. hooks.afterAuthruns after a successful OAuth callback and before the default redirect middleware. If it sends a response, the default redirect is skipped.- Embedded apps served over HTTPS default to
SameSite=None; Securecookies so the OAuth state survives the iframe-to-top-window redirect. The library also keeps a short-lived server-side copy of the OAuth state and uses it as a fallback when the browser does not return the OAuth cookie. For local HTTP development, use an HTTPS tunnel or overridecookieOptionscarefully. - For embedded redirects, the package falls back to top-window redirects unless you provide a custom
embeddedAppUrlbuilder. - Webhook signature verification expects the raw request body. Mount webhook routes before JSON body parsing, or use
express.raw({ type: "*/*" }).
