Seeking URL() replacement for deprecated parse() in Next.js createServer() - javascript

The method for creating a custom server for Next.js is provided in the official docs:
// server.js
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/a', query)
} else if (pathname === '/b') {
app.render(req, res, '/b', query)
} else {
handle(req, res, parsedUrl)
}
}).listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
However, parse() is now flagged as deprecated. The official replacement for parse is the WHATWG URL api.
But the URL class object is not usable by handle() function within the createServer() function (i.e., the parsedUrl object created using parse(req.url, true) cannot be used by handle(req, res, parsedUrl)).
How can the code above be rewritten to use the URL api instead of parse? (Or is there a different solution without either URL or parse?)

Related

How come fetch only works here when I add an alert to the end of the line? Express + NodeJS + Fetch. What's a good fix here

I'm using NodeJS w/ Express to create a web app that records your audio using the VMSG library and posts the BLOB audio to my file system using HTTP Requests and multer. It also adds that instance of a recording into a MongoDB database.
I'm having an issue with the fetch command. It's not working unless I put an alert right after the fetch. The way I have it set up is that I have my main express app (index.js), and a router to the /recordingsDirectory (recordings.js) which is the endpoint for processing the posts. My main index HTML page uses Handlebars and uses a separate JS script (recorder.js) to 1) use the VMSG library and 2) fetch a POST to the /recordingsDirectory once someone submits the audio file w/ the name and the AudioBlob present. This is where I'm stuck. I can fetch in recorder.js with an alert line after the fetch, but I can't have the fetch on the end of the else if block by itself. I'd like to do it without this since the alert is ugly. A solution I've tried is that I tried to make the onsubmit function async and await fetch since I thought maybe it's waiting for a promise but that didn't work.
Here are the files. I commented CRITICAL and SUPER CRITICAL to the lines of code that you should check out and I think where the issues lie:
index.js
const express = require('express')
const handlebars = require('express-handlebars')
const path = require('path')
const XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest
const xhr = new XMLHttpRequest()
const db = require('./db')
const app = express()
const PORT = process.env.PORT || 8000
app.set('view engine', 'hbs')
app.engine('hbs', handlebars({
layoutsDir: path.join(__dirname, 'views', 'layouts'),
extname: 'hbs',
defaultLayout: 'index',
partialsDir: path.join(__dirname, 'views', 'partials'),
}))
app.use(express.json())
app.use(express.urlencoded({extended: false}))
app.use((err, req, res, next) => {
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
return res.status(400).send({ status: 404, message: err.message })
}
next()
})
app.get('/', (req, res) => {
res.render('main', {
title: 'Main Page'
})
})
app.get('/recordings', (req, res) => {
var database = db.get().db('AudioJungle')
database.collection('recordings').find().sort({ "date": -1 }).toArray(function(err, docs) {
res.render('recordings', {
title: 'Recordings',
recordings: docs
})
})
})
// CRITICAL
app.use('/recordingsDirectory', require('./recordings/recordings'))
app.use(express.static('public'))
app.use('/scripts', express.static(path.join(__dirname, 'node_modules', 'vmsg')))
db.connect(function(err) {
if (err) {
console.log('Unable to connect to Mongo.')
process.exit(1)
} else {
app.listen(PORT, () => console.log(`Listening on Port: ${PORT}`))
}
})
process.on('SIGINT', function() {
db.close(function () {
console.log('Disconnected on app termination');
process.exit(0);
});
});
app.use((req, res, next) => {
res.status(404).send({
status: 404,
error: 'Not found'
})
})
recordings.js (Aka the /recordingsDirectory endpoint for a fetch POST)
const express = require('express')
const router = express.Router()
const multer = require('multer')
const fs = require('fs-extra')
const db = require('../db')
const { ObjectId } = require('bson')
const moment = require('moment')
const upload = multer({
storage: multer.diskStorage({
destination: (req, file, callback) => {
let path = './public/uploads'
fs.mkdirsSync(path)
callback(null, path)
},
filename: (req, file, callback) => {
createRecording(req).then((id) => {
var file_name = id + '.mp3'
callback(null, file_name)
})
}
})
})
var type = upload.single('audio-file')
// CRITICAL
router.post('/', type, (req, res) => {
console.log('made it')
res.status(200)
res.send('OK')
})
router.delete('/delete', (req, res) => {
deleteRecording(req.body._id).then((dbResponse) => {
if (dbResponse == null || dbResponse == undefined) {
res.status(400).json({ msg: 'ID already deleted' })
} else {
res.status(200)
}
})
})
router.get('/', (req, res) => {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
recordings.findOne({"_id": ObjectId(req.query.id)}, function(err, result) {
if (err) throw err
if (result == null || result == undefined) {
return res.status(400).json({
status: 404,
error: 'Recording no longer in the database'
})
}
res.status(200)
res.json({
name: result.name,
date: result.date
})
})
})
async function createRecording(req) {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
var audioObject = {
name: req.body.name,
date: moment().format('MMMM Do YYYY, h:mm:ss a')
}
var dbResponse = await recordings.insertOne(audioObject)
return dbResponse.insertedId
}
async function deleteRecording(id) {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
var audioToDelete = {
_id: ObjectId(id)
}
var deleteResult = await recordings.deleteOne(audioToDelete)
return deleteResult
}
module.exports = router
And below is the Script the audio and name and tries to Fetch (where I need the alert for it to actually process into the /recordingsdirectory)
recorder.js
import { record } from "/scripts/vmsg.js";
let recordButton = document.getElementById("record");
var blobObj = null
recordButton.onclick = function() {
record({wasmURL: "/scripts/vmsg.wasm"}).then(blob => {
blobObj = blob
var tag = document.createElement("p")
tag.id="finishedRecording"
var text = document.createTextNode("Audio File Recorded")
tag.appendChild(text)
var element = document.getElementById("box")
element.appendChild(tag)
document.getElementById('box').appendChild(a)
})
}
let form = document.getElementById('mp3Form');
form.addEventListener("submit", submitAudio)
function submitAudio() {
var fileName = form.elements[0].value
if (fileName == "") {
alert('Please enter a name for your file')
} else if (blobObj != null) {
// CRITICAL
// SUPER CRITICAL WHERE FETCH DOESN'T WORK UNLESS I PUT AN ALERT AT THE END
const formData = new FormData()
formData.append('name', fileName)
formData.append('audio-file', blobObj)
const options = {
method: 'POST',
body: formData
}
fetch('/recordingsDirectory', options);
// If I comment an alert here, /recordingsDirectory will process the post since it console.logs 'made it'
} else {
alert('Record some Audio to upload')
}
}
Here's my file system.
Also, I'd like to mention that the fetch works properly on my Windows PC without having to add the alert, but it doesn't work without the alert on my macbook. If any one figures out a fix or an error in how I'm doing things to allow this please let me know. I've been stuck on this problem for a day now. Thanks a bunch!

Express middleware for express.static() not routing correctly and throwing next() is not a function

Despite this running correctly on my local machine while I was writing and testing it, it seems to fail when deployed out to our UAT environment using Docker. I can't seem to track down why it's giving me the following:
next() is not a function
I also seem to be having a routing issue (again only when it's deployed) where even if it when the route is /media/public it seems to still hit the /media route.
dependencies
"dotenv": "^8.2.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1"
server.js
require('dotenv').config();
const express = require('express');
const { join } = require('path');
const { fileExists, isAuthenticated } = require('./helpers/utils');
const { PORT, ROOT_SHARE } = process.env;
const app = express();
global.__basedir = __dirname
app.use('/robots.txt', function (_req, res) {
res.type('text/plain');
res.send('User-agent: *\nDisallow:/');
});
app.use('/media/public', [fileExists, express.static(join(__dirname, ROOT_SHARE, '/public'))]);
app.use('/media', [isAuthenticated, express.static(join(__dirname, ROOT_SHARE))]);
app.listen(PORT, () => console.info(`[File Server] : running on ${PORT}`));
utils.js
const { existsSync } = require('fs');
const { verify } = require('jsonwebtoken');
const { join } = require('path');
const { ACCESS_SECRET, NODE_ENV } = process.env;
const fileExists = async (req, res, next) => {
let mediaFile = (NODE_ENV === 'local') ? join(__basedir, req.baseUrl, req.path).replace('\\src','') : req.originalUrl;
console.log(mediaFile);
if (!existsSync(mediaFile)) {
console.log('NOT FOUND')
return res.status(404).send('NOT FOUND');
}
return next();
}
const isAuthenticated = async (req, res, next) => {
const accessToken = valueOrNull((req.cookies && req.cookies['x-access-token']) || (req.headers && req.headers['x-access-token']));
if (!accessToken) {
console.log('accessToken not found')
return res.status(401).send('UNAUTHORIZED');
}
try {
fileExists(req);
const { user } = verify(accessToken, ACCESS_SECRET);
if (user) {
console.log('VALID USER');
}
} catch (err) {
console.log(err)
return res.status(401).send('UNAUTHORIZED');
}
return next();
};
const valueOrNull = (value) => {
return (typeof value == 'undefined' || value === null || value === '') ? undefined : value;
}
module.exports = {
fileExists,
isAuthenticated
}
.env
ROOT_SHARE=../media
Dockerfile
FROM node:latest
RUN mkdir -p /media
WORKDIR /app
COPY package.json .
COPY package-lock.json .
RUN npm install && npm cache clean --force --loglevel=error
RUN npm install -g pm2
COPY src /app/src
EXPOSE 3000
ENTRYPOINT ["node", "src/server.js"]
exact error
Any help would be greatly appreciated
Your fileExists is being used both as a middleware and as a function called in isAuthenticated — in isAuthenticated it's not being passed res or next. If you do pass it there, you may end up having other issues, since both functions call res.status(status).send(). You also have some async keywords on those functions when they're not awaiting anything — that's not breaking anything, it's just a waste of a few characters :).
You call:
fileExists(req);
but your implementation of fileExists() expects you to pass it (res, res, next) and you are not passing that. Thus, you get the error next() is not a function when fileExists() tries to call next().
It's not clear to me exactly what you're trying to do, but perhaps you can change to this where you pass (req, res, callback) to fileExists() and then you continue your processing in that callback. That's how you manually call middleware yourself which it appears you are trying to do:
const isAuthenticated = (req, res, next) => {
const accessToken = valueOrNull((req.cookies && req.cookies['x-access-token']) || (req.headers && req.headers['x-access-token']));
if (!accessToken) {
console.log('accessToken not found')
return res.status(401).send('UNAUTHORIZED');
}
try {
fileExists(req, res, () => {
const { user } = verify(accessToken, ACCESS_SECRET);
if (user) {
console.log('VALID USER');
}
return next();
});
} catch (err) {
console.log(err)
return res.status(401).send('UNAUTHORIZED');
}
};

Express - Edit HTML before serving as static server

Before I call expressApp.use(express.static(path.join(__dirname, '/../frontend/dist'))); I need to modify the html-code. What I basically need to do is inserting meta tags in two middleware functions. I figured out how to do this. But with my solution I call a middleware-functions inside another one.
app.js
let frontend = await fs
.readFileSync(path.join(__dirname, '/../frontend/dist/index.html'))
.toString('utf8');
expressApp.use((req, res, next) => {
//...
frontend = frontend.replace(
'<meta device="ABC" />',
'<head><meta device="' + deviceId + '"/>'
);
next();
});
expressApp.use((req, res, next) => {
const language = req.get('language') || 'en_GB';
logger.info('language:' + language);
this._languageModule.setLanguage(language);
frontend = this._languageModule.insertSIDs(frontend);
logger.info(frontend);
expressApp.use(express.static(path.join(__dirname, '/../frontend/dist'))); // nested middleware function
next();
});
/** set up all the express routes **/
expressApp.get('/', (req, res) => {
res.send(frontend);
});
Edit
If I don't call expressApp.use(express.static(path.join(__dirname, '/../frontend/dist'))); nested - like this:
expressApp.use((req, res, next) => {
const language = req.get('language') || 'en_GB';
logger.info('language:' + language);
this._languageModule.setLanguage(language);
frontend = this._languageModule.insertSIDs(frontend);
logger.info(frontend);
next();
});
expressApp.use(express.static(path.join(__dirname, '/../frontend/dist')));
the HTML will not be served modified.
You probably should write your own middleware that handles the modification of the files. Here's an example not tested. But it's rough. It's based on the express.static function
const fs = require("fs");
var parseUrl = require('parseurl')
app.use((req, res, next) => {
var originalUrl = parseUrl.original(req)
var path = parseUrl(req).pathname
// make sure redirect occurs at mount
if (path === '/' && originalUrl.pathname.substr(-1) !== '/') {
path = ''
}
// We only answer to GET
if (req.method !== 'GET' && req.method !== 'HEAD') {
return next()
}
let path = path;
fs.exists(path, (exists) => {
if(!exists)
{
// file don't exists skip this middleware
return next();
}
fs.readFile(path, (err, data) => {
if (err)
{
// Can't send the file skip this middle ware
return next();
}
// Do whatever you need with the file here?
// ...
// Setup mime type of the file
res.setHeader("content-type", "text/html");
// send the client the modified html
res.send(data);
});
console.log(exists ? 'it\'s there' : 'no passwd!');
});
});
For the original source please take a look at this github page:
https://github.com/expressjs/serve-static/blob/master/index.js

JS: How can i save a value from a return nested function

I use NextJS and React. My server.js file, looks like this:
const express = require('express')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
var countrycode = ''
server.post('/', (req, res) => {
countrycode = req.body.code
})
server.get('/', (req, res) => {
console.log(res)
if (countrycode == 'DE') {
return app.render(req, res, '/de', req.query)
} else {
return app.render(req, res, '/', req.query)
}
})
})
})
i try to save the req.body.code value inside the outside variable var countrycode but it doesn't work. I need to take this step so I can check this value in the server.get function. If the client comes from Germany, the German side should be returned, otherwise the English.
Where is my mistake? What exactly do I need to change?
Thanks for your answer
If you are trying to serve a specific version of your application based on the user's language preference, you can use the Accept-Language header which is sent by the browser. This header contains the preferred languages of the user as configured in the browser. For convenience it I would recommend using some kind of Express middleware like express-request-language.
In your case this could would look something like this:
const express = require('express')
const next = require('next')
const requestLanguage = require('express-request-language')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
app.prepare()
.then(() => {
const server = express()
server.use(requestLanguage({
languages: ['en-US', 'de-DE']
}))
server.get('/', (req, res) => {
switch (req.language) {
case 'de-DE':
return app.render(req, res, '/de', req.query)
default:
return app.render(req, res, '/', req.query)
}
})
})
It's difficult to be sure without seeing the portion of code where you attempt to use the value stored in countrycode.
My guess is that you're trying to read the value before it is set by the function. For example:
var countrycode;
server.post('/', (req, res) => {
countrycode = req.body.code
})
console.log(countrycode);
Here, the console output will be undefined, because countrycode will not be set until the POST completes and the function sets its value. To fix this, you should look into using a promise which will only resolve once the value of countrycode has been set.
server.post() is an asynchronous function, which returns immediately and cannot assign values to variables in an outer synchronous code block. Restructure your code to deal with asynchronous behavior:
app.prepare().then(() => {
const server = express()
return new Promise((resolve, reject) => {
server.post('/', (req, res) => {
resolve(req.body.code);
})
});
}).then(countrycode => {
console.log('server responded with countrycode:', countrycode);
})
Alternatively:
app.prepare()
.then(() => {
const server = express()
return new Promise(resolve => {
server.post('/', (req, res) => {
resolve(req.body.code);
})
})
.then(countrycode => {
return new Promise(resolve => {
server.get('/', (req, res) => {
console.log(res)
if (countrycode == 'DE') {
resolve(app.render(req, res, '/de', req.query))
} else {
resolve(app.render(req, res, '/', req.query))
}
})
})
})
})

Function executed, but throwing error

Trying to write the following code:
index.js
const http = require('http');
const port = 9090;
const url = require('url');
const handlers = require('./handlers.js');
http.createServer((req, res) => {
req.path = url.parse(req.url).pathname;
let handler = handlers(req.path);
handler(req, res);
}).listen(port);
handlers.js:
const fs = require('fs');
const homeHandler = require('./handlers/home-handler');
const contentHandler = require('./handlers/content-handler');
module.exports = (path) => {
switch (path) {
case '/':
return homeHandler;
case '/content':
return contentHandler;
}
}
home-handler.js
const fs = require('fs')
module.exports = (req, res) => {
fs.readFile('./index.html', (err, data) => {
if (err) {
console.log(err.message);
return;
}
res.writeHead(200, {
'content-type': 'text/html'
});
res.write(data);
res.end();
return;
});
};
When I "Launch program" and run in browser localhost:9090, the function handler is executed in browser, but in the debug console it throws:
TypeError: handler is not a function
Using console.log(handler) it shows that handler is function, also instanceof shows that handler is instance of Function. What's wrong with this?
Your handlers function only handle two specific inputs, / and /content. Any other request will produce an error, since handlers will not return a function.
You might say, "That's fine! I'm not requesting a path outside of that set of two paths," but I think you might be wrong.
Most browsers will make a request for a favicon by requesting /favicon.ico in addition to making a request for the actual URL you typed in. If the browser does this additional favicon request, you will see successful results for you intended request (as you do), but then also see a failure message for the additional favicon request, which you haven't set up your code to handle.
I suggest adding a default debug handler:
module.exports = (path) => {
switch (path) {
case '/':
return homeHandler;
case '/content':
return contentHandler;
default:
(req, res) => {
res.end("Path not handled");
console.warn("No handler for", req.url);
}
}
}
const fs = require('fs')
const faviconIco = '/favicon.ico'
module.exports = (req, res) => {
fs.readFile('.' + faviconIco, (err, data) => {
if (err) {
console.log(err.message)
return
}
res.writeHead(200, {
'content-type': 'image/x-icon'
})
res.write(data)
res.end()
})
}

Categories