I am trying to host a NextJS app and everything seems to be working fine locally. I am able to get the data from the site and I can go to the site and see the raw json that is being returned, but when I try to get things working on production the API is completely inaccessible through the browser and through the Axios requests.
The server just returns 500 or Internal Server Error.
I have tried deploying on DigitalOcean App Platform and AWS Amplify, but both fail to connect to the API routes.
I followed this tutorial for the NextJS SSR method that says to build and start using
// next.config.js
const path = require('path')
const Dotenv = require('dotenv-webpack')
require('dotenv').config()
module.exports = {
webpack: (config) => {
config.plugins = config.plugins || []
config.module.rules.push({
test: /\.svg$/,
use: ["#svgr/webpack"]
});
config.plugins = [
...config.plugins,
// Read the .env file
new Dotenv({
path: path.join(__dirname, '.env'),
systemvars: true
})
]
return config
},
sassOptions: {
includePaths: [path.join(__dirname, 'styles')]
}
}
// package.json
...
"scripts": {
"dev": "next dev",
"build": "next build",
"digitalocean": "next start -H 0.0.0.0 -p ${PORT:-8080}",
"start": "next start"
},
...
// api.js
const axios = require('axios')
const {getS3URL} = require('./aws')
require('dotenv').config()
export default async (req, res) => {
const config = {
bucket: 'bucket',
key: 'folder/data.json'
}
const request = await axios.get(await getS3URL(config));
try {
res.status(200).json(JSON.stringify(request.data))
} catch {
res.status(500).json({ error: '500', response })
res.status(400).json({ error: '400', response })
}
}
// frontend.js
...
const getData = async () => {
console.log(`${host}api/daily-trip-stats`)
const trips = await axios.get(`${host}api/daily-trip-stats`)
const routes = await axios.get(`${host}api/daily-route-stats`)
const stops = await axios.get(`${host}api/daily-stops-routes`)
const cleanUp = async (data) => {
return await data.map(fea => fea.properties)
}
return {
routes: await cleanUp(routes.data.features),
trips: await cleanUp(trips.data.features),
stops: await cleanUp(stops.data.features)
}
};
...
Checked the server logs and found that the default region was not being set properly.
var { S3Client, GetObjectCommand, Config} = require('#aws-sdk/client-s3');
import { getSignedUrl } from "#aws-sdk/s3-request-presigner";
const getS3URL = async ({bucket, key}) => {
const client = new S3Client({
region: 'us-east-1' // !!! FORGOT TO SET THE DEFAULT REGION
})
var params = {
Bucket: bucket,
Key: key,
Expires: 60,
ContentType: 'blob'
};
const s3Data = new GetObjectCommand(params);
const url = await getSignedUrl(client, s3Data, { expiresIn: 3600 });
return url
};
module.exports = {getS3URL}
Related
Currently I am trying to deploy my Shopify Custom App to Fly.io. Installing this app is succeeding on my development store but I get an error right after with the oAuth callback with status code 400. This is the URL it shows upon installing:
https://appname.fly.dev/api/auth/callback?code=71bfdaadd63b87eb72d9d3dc516ea1ea&hmac=1efd4ff63ebca8f28c733f464ded354ba2f0995aeb1910114e0139eaefd4cce3&host=YWRtaW4uc2hvcGlmeS5jb20vc3RvcmUvc2hvb3B5bG9vcHkx&shop=shoopyloopy1.myshopify.com&state=920113322594675×tamp=1676563785
With text in body: Invalid OAuth callback.
The app works with all the callbacks working with a ngrok tunnel during development. Just not when deployed to fly.io. The apps frontend also works after deployment to fly.io, but all the api and auth callbacks fail to work. I get the following response on those API calls:
On performing API calls on the /api/ route I get the following error in the return of the api call:
Failed to parse session token 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczpcL1wvc2hvb3B5bG9vcHkxLm15c2hvcGlmeS5jb21cL2FkbWluIiwiZGVzdCI6Imh0dHBzOlwvXC9zaG9vcHlsb29weTEubXlzaG9waWZ5LmNvbSIsImF1ZCI6IjU4YTAzZjkwZTk4Yjc5NGRlZmE5NDZlMWZiNmVlMzRiIiwic3ViIjoiNzQ3NzAxNTM2NjEiLCJleHAiOjE2NzY1NjQyMjYsIm5iZiI6MTY3NjU2NDE2NiwiaWF0IjoxNjc2NTY0MTY2LCJqdGkiOiI0OTcyNDEwOC0zNWQ2LTRjODEtOWJkNS0wZWRkMWM4MWIxMDYiLCJzaWQiOiIxOGZmZjg5NTMyZGRiODdiOWQ3OTBhYmY1M2EwOTZiMDNkNmE4ZWU1ZTA0ZmRjZmFmOWUxOWM2OGQxZGFjN2Q2In0.XeuA5W95YjjVLZYOvmRJ9a90xpPNEukhNQ1_z4Kw_xA': signature verification failed
My fly.toml file:
app = "appname"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
[env]
PORT = "8081"
HOST = "https://appname.fly.dev"
SHOPIFY_API_KEY = "58a03f90e98b794defa946e1fb6ee34b"
SCOPES = "write_products,read_script_tags,write_script_tags"
[experimental]
auto_rollback = true
[[services]]
http_checks = []
internal_port = 8081
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
My index.js file starting up express:
// #ts-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 {addScriptTag, deleteScriptTag, getProductInfo, getProductsFromIds, getScriptTags} from "./graph-functions.js";
const PORT = parseInt(process.env.BACKEND_PORT || process.env.PORT, 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 })
);
// All endpoints after this point will require an active session
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/get-product", async (_req, res) => {
const products = await getProductInfo(res.locals.shopify.session, _req.query.id);
res.status(200).send(products);
});
app.get("/api/get-products", async (_req, res) => {
const products = await getProductsFromIds(res.locals.shopify.session,_req.query.ids);
res.status(200).send(products);
});
app.get("/api/add-script", async (_req, res) => {
const data = await addScriptTag(res.locals.shopify.session,_req.query.src);
console.log(_req.query.ids);
res.status(200).send(data);
});
app.get("/api/get-scripts", async (_req, res) => {
const data = await getScriptTags(res.locals.shopify.session);
res.status(200).send(data?.body?.data ? data?.body?.data : data);
});
app.get("/api/delete-script", async (_req, res) => {
const data = await deleteScriptTag(res.locals.shopify.session,_req.query.id);
res.status(200).send(data);
});
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 });
});
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);
Any help would be highly appreciated.
I followed the official documentation: Shopify Official Docs
The Dockerfile has the same port 8081 as assigned to in the fly.toml file.
Edit (Added shopify app implementation with Database):
import { LATEST_API_VERSION } from "#shopify/shopify-api";
import { shopifyApp } from "#shopify/shopify-app-express";
import { SQLiteSessionStorage } from "#shopify/shopify-app-session-storage-sqlite";
import { restResources } from "#shopify/shopify-api/rest/admin/2023-01";
const DB_PATH = `${process.cwd()}/database.sqlite`;
const shopify = shopifyApp({
api: {
apiVersion: LATEST_API_VERSION,
restResources,
billing: undefined, // or replace with billingConfig above to enable example billing
},
auth: {
path: "/api/auth",
callbackPath: "/api/auth/callback",
},
webhooks: {
path: "/api/webhooks",
},
// This should be replaced with your preferred storage strategy
sessionStorage: new SQLiteSessionStorage(DB_PATH),
});
export default shopify;
I've created a plugin in shopify using node.js & vite.js.
shopify app create node
After running using npm run dev, it generates a url like this: https://b136-0000-7400-56-bc78-5000-178b-d6f3-6000.ngrok.io/login?shop=shopname.myshopify.com
When I open this link, it start reloading infinite with error
This is my index.js:
import { resolve } from "path";
import express from "express";
import cookieParser from "cookie-parser";
import { Shopify, LATEST_API_VERSION } from "#shopify/shopify-api";
import "dotenv/config";
import applyAuthMiddleware from "./middleware/auth.js";
import verifyRequest from "./middleware/verify-request.js";
const USE_ONLINE_TOKENS = true;
const TOP_LEVEL_OAUTH_COOKIE = "shopify_top_level_oauth";
const PORT = parseInt(process.env.PORT || "8081", 10);
const isTest = process.env.NODE_ENV === "test" || !!process.env.VITE_TEST_BUILD;
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
API_VERSION: LATEST_API_VERSION,
IS_EMBEDDED_APP: true,
// This should be replaced with your preferred storage strategy
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};
Shopify.Webhooks.Registry.addHandler("APP_UNINSTALLED", {
path: "/webhooks",
webhookHandler: async (topic, shop, body) => {
delete ACTIVE_SHOPIFY_SHOPS[shop];
},
});
// export for test use only
export async function createServer(
root = process.cwd(),
isProd = process.env.NODE_ENV === "production"
) {
const app = express();
app.set("top-level-oauth-cookie", TOP_LEVEL_OAUTH_COOKIE);
app.set("active-shopify-shops", ACTIVE_SHOPIFY_SHOPS);
app.set("use-online-tokens", USE_ONLINE_TOKENS);
app.use(cookieParser(Shopify.Context.API_SECRET_KEY));
applyAuthMiddleware(app);
app.post("/webhooks", async (req, res) => {
try {
await Shopify.Webhooks.Registry.process(req, res);
console.log(`Webhook processed, returned status code 200`);
} catch (error) {
console.log(`Failed to process webhook: ${error}`);
if (!res.headersSent) {
res.status(500).send(error.message);
}
}
});
app.get("/products-count", verifyRequest(app), async (req, res) => {
const session = await Shopify.Utils.loadCurrentSession(
req,
res,
app.get("use-online-tokens")
);
const { Product } = await import(
`#shopify/shopify-api/dist/rest-resources/${Shopify.Context.API_VERSION}/index.js`
);
const countData = await Product.count({ session });
res.status(200).send(countData);
});
app.post("/graphql", verifyRequest(app), async (req, res) => {
try {
const response = await Shopify.Utils.graphqlProxy(req, res);
res.status(200).send(response.body);
} catch (error) {
res.status(500).send(error.message);
}
});
app.use(express.json());
app.use((req, res, next) => {
const shop = req.query.shop;
if (Shopify.Context.IS_EMBEDDED_APP && shop) {
res.setHeader(
"Content-Security-Policy",
`frame-ancestors https://${shop} https://admin.shopify.com;`
);
} else {
res.setHeader("Content-Security-Policy", `frame-ancestors 'none';`);
}
next();
});
app.use("/*", (req, res, next) => {
const { shop } = req.query;
// Detect whether we need to reinstall the app, any request from Shopify will
// include a shop in the query parameters.
if (app.get("active-shopify-shops")[shop] === undefined && shop) {
res.redirect(`/auth?${new URLSearchParams(req.query).toString()}`);
} else {
next();
}
});
/**
* #type {import('vite').ViteDevServer}
*/
let vite;
if (!isProd) {
vite = await import("vite").then(({ createServer }) =>
createServer({
root,
logLevel: isTest ? "error" : "info",
server: {
port: PORT,
hmr: {
protocol: "ws",
host: "localhost",
port: 64999,
clientPort: 64999,
},
middlewareMode: "html",
},
})
);
app.use(vite.middlewares);
} else {
const compression = await import("compression").then(
({ default: fn }) => fn
);
const serveStatic = await import("serve-static").then(
({ default: fn }) => fn
);
const fs = await import("fs");
app.use(compression());
app.use(serveStatic(resolve("dist/client")));
app.use("/*", (req, res, next) => {
// Client-side routing will pick up on the correct route to render, so we always render the index here
res
.status(200)
.set("Content-Type", "text/html")
.send(fs.readFileSync(`${process.cwd()}/dist/client/index.html`));
});
}
return { app, vite };
}
if (!isTest) {
createServer().then(({ app }) => app.listen(PORT));
}
The app is installing fine but it's getting refreshed again and again due to failed connection to ws (as mentioned in the screenshot). I tried a few things around changing the settings of the HMR but doesn't seem to be connecting.
I am testing an api with all http 500 errors.
Here I try to use sinon.stub to test on a failing server and get a 500 error, but I get a timeOut async callback, or if I use my app a successfull 200 response statusCode as if sinon.stub has no effect. I must miss something and I am stucked...
would you see a horrifying error below ?
thanks for your precious help
process.env.NODE_ENV = "test";
const app = require("../../app");
const request = require("supertest");
const sinon = require("sinon");
// /************************** */
const usersRoute = require("../../routes/Users");
const express = require("express");
const initUsers = () => {
const app = express();
app.use(usersRoute);
return app;
};
describe("all 5xx errors tested with stub", function () {
it("should return a 500 when an error is encountered", async (done) => {
let secondApp;
sinon.stub(usersRoute, "post").throws(
new Error({
response: { status: 500, data: { message: "failed" } },
})
);
secondApp = initUsers(); //==========> Timeout Async Callback
//secondApp = require("../../app"); //==============> gives a 200 instead of 500
const fiveHundredError = await request(secondApp)
.post("/users/oauth?grant_type=client_credentials")
.send({
username: "digitalAccount",
password: "clientSecret",
});
expect(fiveHundredError.statusCode).toBe(500);
//sinon.restore();
done();
});
});
app is using express.Router to get users route :
const express = require("express");
const router = express.Router();
const axios = require("axios");
router.post("/users/oauth", async (req, res) => {
//if (all missing parts)
//else {
try {
if (req.fields) {
const response = await axios.post(
`${base_url}oauth/token?grant_type=${req.query.grant_type}`,
{},
{
auth: {
username: req.fields.username,
password: req.fields.password,
},
}
);
res.json(response.data);
}
} catch (error) {
return res.status(error.response.status).json(error.response.data);
}
}
});
module.exports = router;
See server.js :
const app = require("./app");
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`server starting on port ${port}!`));
and app.js :
// private environment:
require("dotenv").config();
const express = require("express");
const formidableMiddleware = require("express-formidable");
const cors = require("cors");
const app = express();
app.use(formidableMiddleware());
app.use(cors());
const usersRoute = require("./routes/Users");
app.use(usersRoute);
app.get("/", (req, res) => {
res.status(200).send("Welcome to Spark Admin back end!");
});
app.all("*", (req, res) => {
return res.status(404).json({ error: "Web url not found" });
});
module.exports = app;
I finally opted for 'nock' and deleted 'sinon'
const nock = require("nock");
const axios = require("axios");
describe("POST login: all 5xx errors tested with nock", function () {
it("should return a 500 when an error is encountered", async (done) => {
const scope = nock("http://localhost:5000")
.post(
"/users/oauth",
{},
{
username: "blibli",
password: "blabla",
}
)
.reply(500, {
response: {
statusCode: 500,
body: { error: "AN ERROR OCCURED" },
},
});
try {
await axios.post(
"http://localhost:5000/users/oauth",
{},
{
username: "blibli",
password: "blabla",
}
);
} catch (e) {
expect(e.response.status).toBe(500);
}
done();
});
});
I'm build vue app, and for mine app need api request to server from client, also necessary proxy any request.
It's mine vue.config.js
const producer = require('./src/kafka/producer');
const bodyParser = require('body-parser')
module.exports = {
devServer: {
setup: function (app, server) {
app.use(bodyParser.json())
app.post('/send-message', function (req, res) {
producer.send(req.body)
.then(() => {
res.json({result: true, error: null});
})
.catch((e) => {
res.status(500).json({result: false, error: e});
})
});
},
proxy: {
'/v2/order/by-number': {
target: 'http://address-here'
}
}
}
};
As you can see so i'm use body-parser app.use(bodyParser.json())
After I added it, proxying stopped working for me. Request to /send-message freezes after show me error
Proxy error: Could not proxy request path-here from localhost:8080
to http://address-here
Internet searches have not led to a solution.
For a long time, i find a solution:
Add second param jsonParser to app.post()
See full example
const producer = require('./src/kafka/producer');
const bodyParser = require('body-parser')
const jsonParser = bodyParser.json({limit: '1mb'});
module.exports = {
devServer: {
setup: function (app, server) {
app.post('/send-message', jsonParser, function (req, res) {
producer.send(req.body)
.then(() => {
res.json({result: true, error: null});
})
.catch((e) => {
res.status(500).json({result: false, error: e});
})
});
},
proxy: {
'path': {
target: 'http://address-here'
}
}
}
};
I have setup a express.js + next.js app, which is running fine in development environment. When I am trying to run its webpack bundle, Its throwing error
Error: Cannot find module '/Users/user/workspace/project/next.config.js'
I am trying to run its bundle as aws-lamda is not allowing me to upload zip size more than 50MB.
// server.js
const express = require('express');
const argv = require('yargs').argv;
const nextApp = require('./nextApp.js');
const handle = nextApp.getRequestHandler();
const pageRoutes = require('./routes/pages/index.js');
const port = argv.port || 3000;
const server = express();
// route to next.js web pages
server.use('/', pageRoutes);
server.get('*', (req, res) => {
return handle(req, res)
});
nextApp.prepare()
.then(() => {
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
});
})
.catch((ex) => {
console.error(ex.stack)
process.exit(1)
});
module.exports = server;
So far I have found that we can/should extends next.config.js for adding additional bundle entries instead of creating a separate webpack.config.js. Following config will create a serverbundle.js file in build/server directory.
const merge = require('webpack-merge');
module.exports = {
distDir: 'build',
webpack (config, {isServer}) {
if (isServer) {
return merge(config, {
entry () {
return config.entry().then((entry) => {
return Object.assign({}, entry, { serverbundle: './server' })
})
},
output: {
filename: '[name].js'
}
});
}
return config;
}
}