Next.js provides serverless API routes. By creating a file under ./pages/api you can have your service running, and I want to have a Socket.io service by using this mechanism.
I have created a client:
./pages/client.js
import { useEffect } from 'react';
import io from 'socket.io-client';
export default () => {
useEffect(() => {
io('http://localhost:3000', { path: '/api/filename' });
}, []);
return <h1>Socket.io</h1>;
}
And an API route:
./pages/api/filename.js
const io = require('socket.io')({ path: '/api/filename' });
io.onconnection = () => {
console.log('onconnection');
}
io.on('connect', () => {
console.log('connect');
})
io.on('connection', () => {
console.log('connection');
})
export default (req, res) => {
console.log('endpoint');
}
But I can't get the client to connect to the Socket.io server and succesfully see any of: 'onconnection', 'connect', or 'connection' printed.
The trick is to plug 'socket.io' into the http server only once, so checking every access to the api.
Try something like this:
./pages/api/socketio.js
import { Server } from 'socket.io'
const ioHandler = (req, res) => {
if (!res.socket.server.io) {
console.log('*First use, starting socket.io')
const io = new Server(res.socket.server)
io.on('connection', socket => {
socket.broadcast.emit('a user connected')
socket.on('hello', msg => {
socket.emit('hello', 'world!')
})
})
res.socket.server.io = io
} else {
console.log('socket.io already running')
}
res.end()
}
export const config = {
api: {
bodyParser: false
}
}
export default ioHandler
./pages/socketio.jsx
import { useEffect } from 'react'
import io from 'socket.io-client'
export default () => {
useEffect(() => {
fetch('/api/socketio').finally(() => {
const socket = io()
socket.on('connect', () => {
console.log('connect')
socket.emit('hello')
})
socket.on('hello', data => {
console.log('hello', data)
})
socket.on('a user connected', () => {
console.log('a user connected')
})
socket.on('disconnect', () => {
console.log('disconnect')
})
})
}, []) // Added [] as useEffect filter so it will be executed only once, when component is mounted
return <h1>Socket.io</h1>
}
You have to have the /api/pusher/auth to authenticate with pusher on the frontend. Then you use the key you get from that to communicate with pusher. It's for security purposes. You can do it all through the frontend, but depending on your app, if you're saving data (such as messages, or chats) then probably should authenticate.
You can use custom server and attach sockets to it (just like with express) and provide needed path where socket.io will listen. How to use custom server
You can write something like this server.js
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const { Server } = require('socket.io');
const dev = process.env.NODE_ENV !== 'production';
const hostname = 'localhost';
const port = 3000;
// when using middleware `hostname` and `port` must be provided below
const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = createServer(async (req, res) => {
try {
// 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') {
await app.render(req, res, '/a', query);
} else if (pathname === '/b') {
await app.render(req, res, '/b', query);
} else {
await handle(req, res, parsedUrl);
}
} catch (err) {
console.error('Error occurred handling', req.url, err);
res.statusCode = 500;
res.end('internal server error');
}
});
const io = new Server(server, {
path: '/socket.io' // or any other path you need
});
io.on('connection', socket => {
// your sockets here
console.log('IO_CONNECTION');
});
server.listen(port, err => {
if (err) throw err;
console.log(`> Ready on http://${hostname}:${port}`);
});
});
You would need to run your server using node server.js
Related
I'm new to service workers and I'm running into an issue with my implementation. My goal is to create a runtime cache for images and videos. I've looked at the workbox implementation but it hasn't worked for me. I see that my service worker successfully registers at the top-level scope of my app but for some reason, it seems like some of the code in my service worker file doesn't get executed. The main issue is that the event listeners from my service worker don't seem to get called (including registerRoute), and therefore, the Cache doesn't ever get created.
I'm not sure if this is related to the issue I'm having but when I look at the console messages, it seems like the code from sw.js may be run prior to the service worker registration:
console messages
I've been stuck on this problem for a while so I would appreciate some help if anyone has run into this issue, thanks!
// main.js (in a Vue 2 app)
if (process.env.NODE_ENV === "production") {
window.addEventListener("load", () => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register(`/sw.js`)
.then(() => {
console.log("Service worker registered!");
navigator.serviceWorker.ready.then((registration) => {
registration.update();
console.log('Service Worker: ready');
});
})
.catch((error) => {
console.warn("Error registering service worker:");
console.warn(error);
});
}
});
}
// sw.js
import { registerRoute } from "workbox-routing";
import { CacheFirst } from "workbox-strategies";
import { CacheableResponsePlugin } from "workbox-cacheable-response";
import { RangeRequestsPlugin } from "workbox-range-requests";
import { clientsClaim } from "workbox-core";
const CACHE_PREFIX = "background-slideshow-cache";
const CACHE_VERSION = "v1";
const CACHE_RUNTIME = "runtime";
const BACKGROUND_SLIDESHOW_CACHE = `${CACHE_PREFIX}-${CACHE_RUNTIME}-${CACHE_VERSION}`;
clientsClaim();
const addToCache = async (url) => {
const cache = await caches.open(BACKGROUND_SLIDESHOW_CACHE);
if (!(await cache.match(url))) {
await cache.add(url);
}
};
const cacheFirstStrategy = new CacheFirst({
cacheName: BACKGROUND_SLIDESHOW_CACHE,
plugins: [
new CacheableResponsePlugin({
statuses: [200],
}),
new RangeRequestsPlugin(),
],
});
self.addEventListener("message", (event) => {
if (event.data && event.data.message) {
if (event.data.message === "SKIP_WAITING") {
self.skipWaiting();
}
}
});
self.addEventListener("install", (event) => {
console.log('Service worker: fetch event', event);
})
console.log("Service Worker: in file");
registerRoute(
({ request }) => {
const { destination } = request;
console.log("Service Worker:", "request", request);
return destination === "video" || destination === "image";
},
({ event, request }) => {
// console.log("Service Worker: in the 2nd param", event, request);
event.respondWith(async () => {
await addToCache(request.url);
return cacheFirstStrategy.handle({ request });
});
}
);
After many hours of debugging, I realized that the minification of sw.js at build time was the reason this code wasn't able to execute. I decided to use uglifyjs-webpack-plugin in my webpack config and this solved the issue!
Normally for nodejs I would run an app in a subfolder using:
app.use(config.baseUrl, router);
However for remix it's abstracted into createRequestHandler:
app.all(
"*",
MODE === "production"
? createRequestHandler({ build: require(BUILD_DIR), })
: (...args) => {
purgeRequireCache();
const requestHandler = createRequestHandler({
build: require(BUILD_DIR),
mode: MODE,
});
return requestHandler(...args);
}
);
Is there any way to configure the base url? Full server.ts:
import path from "path";
import express, { RequestHandler, Request, Response, NextFunction, } from "express";
import compression from "compression";
import morgan from "morgan";
import { createRequestHandler } from "#remix-run/express";
const app = express();
const globalExcludePaths = (req: Request, res: Response, next: NextFunction) => {
const paths = [
'/self-service*'
];
const pathCheck = paths.some(path => path === req.path);
if (!pathCheck) {
console.log('Valid path')
next()
}
else {
console.log('Ignoring path')
}
}
app.use((req, res, next) => {
globalExcludePaths(req, res, next);
})
app.use((req, res, next) => {
// helpful headers:
res.set("Strict-Transport-Security", `max-age=${60 * 60 * 24 * 365 * 100}`);
// /clean-urls/ -> /clean-urls
if (req.path.endsWith("/") && req.path.length > 1) {
const query = req.url.slice(req.path.length);
const safepath = req.path.slice(0, -1).replace(/\/+/g, "/");
res.redirect(301, safepath + query);
return;
}
next();
});
app.use(compression());
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
app.disable("x-powered-by");
// Remix fingerprints its assets so we can cache forever.
app.use(
"/build",
express.static("public/build", { immutable: true, maxAge: "1y" })
);
// Everything else (like favicon.ico) is cached for an hour. You may want to be
// more aggressive with this caching.
app.use(express.static("public", { maxAge: "1h" }));
app.use(morgan("tiny"));
const MODE = process.env.NODE_ENV;
const BUILD_DIR = path.join(process.cwd(), "build");
app.all(
"*",
MODE === "production"
? createRequestHandler({ build: require(BUILD_DIR), })
: (...args) => {
purgeRequireCache();
const requestHandler = createRequestHandler({
build: require(BUILD_DIR),
mode: MODE,
});
return requestHandler(...args);
}
);
const port = process.env.PORT || 3000;
app.listen(port, () => {
// require the built app so we're ready when the first request comes in
require(BUILD_DIR);
console.log(`✅ app ready: http://0.0.0.0:${port}`);
});
function purgeRequireCache() {
// purge require cache on requests for "server side HMR" this won't let
// you have in-memory objects between requests in development,
// alternatively you can set up nodemon/pm2-dev to restart the server on
// file changes, we prefer the DX of this though, so we've included it
// for you by default
for (const key in require.cache) {
if (key.startsWith(BUILD_DIR)) {
// eslint-disable-next-line #typescript-eslint/no-dynamic-delete
delete require.cache[key];
}
}
}
Take a look at remix-mount-routes. This allows you to mount your Remix app to a base path other than root.
https://github.com/kiliman/remix-mount-routes
I have built a test app using nestjs + Sequelize ORM + docker database (as of now local). As per documentation, I am using umzug library and AWS Lambda SAM template and triggering lambda handler. Below is the code for it. Connection Pooling is implemented to reuse existing sequelize connection. Below is the lambdaEntry.ts file where I trigger umzug.up() function. It is triggering but not migrating files.
When done from command prompt node migrate up it works correctly. I am testing using sam invoke command to test it.
require('ts-node/register');
import { Server } from 'http';
import { NestFactory } from '#nestjs/core';
import { Context } from 'aws-lambda';
import * as serverlessExpress from 'aws-serverless-express';
import * as express from 'express';
import { ExpressAdapter } from '#nestjs/platform-express';
import { eventContext } from 'aws-serverless-express/middleware';
import { AppModule } from './app.module';
import sharedBootstrap from './sharedBootstrap';
const { Sequelize } = require('sequelize');
const { Umzug, SequelizeStorage } = require('umzug');
import configuration from '.././config/config';
const fs = require('fs');
let lambdaProxy: Server;
let sequelize = null;
async function bootstrap() {
const expressServer = express();
const nestApp = await NestFactory.create(
AppModule,
new ExpressAdapter(expressServer),
);
nestApp.use(eventContext());
sharedBootstrap(nestApp);
await nestApp.init();
return serverlessExpress.createServer(expressServer);
}
export const handler = (event: any, context: Context) => {
if (!lambdaProxy) {
bootstrap().then((server) => {
lambdaProxy = server;
serverlessExpress.proxy(lambdaProxy, event, context);
(async () => {
if (!sequelize) {
console.log('New connection::');
sequelize = await loadSequelize();
} else {
sequelize.connectionManager.initPools();
if (sequelize.connectionManager.hasOwnProperty('getConnection')) {
delete sequelize.connectionManager.getConnection;
}
}
try {
console.log('MIGRATOR::');
const umzug = new Umzug({
migrations: { glob: 'src/migrations/*.ts' },
context: sequelize.getQueryInterface(),
storage: new SequelizeStorage({ sequelize }),
logger: console,
});
await umzug
.pending()
.then((migrations: any) => {
console.log('pending ? : ', JSON.stringify(migrations));
//test for file exists.
for (const migration of migrations) {
try {
if (fs.existsSync(migration.path)) {
console.log('file exists');
}
} catch (err) {
console.log('file does not exists');
console.error(err);
}
}
async () => {
//BELOW FUNCTION IS TRIGGERING BUT NOT GETTING MIGRATION LOADED.
await umzug.up();
};
})
.catch((e: any) => console.log('error2 ? ', e));
} finally {
await sequelize.connectionManager.close();
}
})();
});
} else {
serverlessExpress.proxy(lambdaProxy, event, context);
}
};
async function loadSequelize() {
const sequelize = new Sequelize(
configuration.database,
configuration.username,
configuration.password,
{
dialect: 'mysql',
host: configuration.host,
port: Number(configuration.port),
pool: {
max: 2,
min: 0,
idle: 0,
acquire: 3000,
evict: 600,
},
},
);
await sequelize.authenticate();
return sequelize;
}
I am able to solve the issue after lot of tries. I seperated out the sequelize connection code and called it from app side and triggered from lambdaentry
lambdaEntry.js file.
async function bootstrap(uuid = null) {
console.log('Calling bootstrap');
const expressServer = express();
const nestApp = await NestFactory.create(
AppModule,
new ExpressAdapter(expressServer),
);
nestApp.use(eventContext());
sharedBootstrap(nestApp);
await nestApp.init();
try {
// Write a function in Service (ex: purhaslistservice) and trigger the service with umzug up from here.
const migrateResult1 = await nestApp.get(PurchaseListService).migrate('down');
console.log(migrateResult1);
const migrateResult2 = await nestApp.get(PurchaseListService).migrate('up');
console.log(migrateResult2);
} catch (err) {
throw err;
}
return serverlessExpress.createServer(expressServer);
}
export const handler = (event: any, context: Context) => {
if (!lambdaProxy) {
bootstrap(uuid).then((server) => {
lambdaProxy = server;
serverlessExpress.proxy(lambdaProxy, event, context);
});
} else {
serverlessExpress.proxy(lambdaProxy, event, context);
}
};
/code/src/purchaselist/purchaselist.service.ts
async migrate(id: string): Promise<any> {
console.log('migrate script triggered', id);
const sequelize = PurchaseListItem.sequelize;
const umzug = new Umzug({
migrations: { glob: 'src/migrations/*.{ts,js}' },
context: sequelize.getQueryInterface(),
storage: new SequelizeStorage({ sequelize }),
logger: console,
});
let consoleDisplay = 'Umzug LOGS:::<br/>';
switch (id) {
default:
case 'up':
await umzug.up().then(function (migrations) {
console.log('Umzug Migrations UP::<br/>', migrations);
consoleDisplay +=
'Umzug Migrations UP::<br/>' + JSON.stringify(migrations);
});
break;
}
return consoleDisplay;
}
I am getting this error in my heroku logs.
Same Question
All the solutions provided here did not address the issue.
I tried the different variations of the get method:
app.use(express.static('build'));
app.get('*', function (req, res) {
res.sendFile('index.html');
});
What else could I try or am I missing from here?
App.js
const configuration = require('#feathersjs/configuration');
const feathers = require('#feathersjs/feathers');
const express = require('#feathersjs/express');
const socketio = require('#feathersjs/socketio');
const moment = require('moment');
class IdeaService {
constructor() {
this.ideas = [];
}
async find() {
return this.ideas;
}
async create(data) {
const idea = {
id: this.ideas.length,
text: data.text,
tech: data.tech,
viewer: data.viewer
};
idea.time = moment().format('h:mm:ss a');
this.ideas.push(idea);
return idea;
}
}
const app = express(feathers());
app.feathers().configure(configuration());
app.use(express.static('build'));
app.get('*', function (req, res) {
res.sendFile('index.html');
});
// Parse JSON
app.use(express.json());
// Configure SocketIO realtime API
app.configure(socketio());
// Enable REST services
app.configure(express.rest());
// Register services
app.use('/ideas', new IdeaService());
// Connect new streams
app.on('connection', conn => app.channel('stream').join(conn));
// Publish events to stream
app.publish(data => app.channel('stream'));
const PORT = process.env.PORT || 3030;
app.listen(PORT).on('listening', () => console.log(`Server running on port ${PORT}`));
app.service('ideas').create({
text: 'Build a cool app',
tech: 'Node.js',
viewer: 'John Doe'
});
export default IdeaService;
package.json
I have an apolloclient with middleware that console.logs a bearer token, because I am not always authenticated when I should be.
For some reason, it appears that queries from the react-apollo <Query /> object use this middleware -- I see my console message -- but queries that I trigger programmatically with: apolloClient.query do not log anything (there's no way for the code to do this, the console log is at the top of the authLink middleware).
I started my project with apollo-boost before switching to apolloclient, so I thought perhaps node_modules was not correctly set up after the switch. But I've removed and reinstalled with yarn, it should not have any vestiges of apollo-boost in there now.
additionally, if I copy the code that I use to create apolloclient into my transaction, making it use that local copy instead of the global one, the middleware DOES fire.
ie:
export const relayBBNToGraphcool = async () => {
/* BEGIN without this code, WHICH IS ALREADY in the instantiation of apolloClient, the result is `user: null` */
const authLink = setContext(async (req, { headers }) => {
// get the authentication token from local storage if it exists
let getToken = async () => await AsyncStorage.getItem(/*'access_token'*/'graphcool_token')
const token = await getToken()
console.trace('token for connection to graphcool is currently', token, req.operationName)
// return the headers to the context so httpLink can read them
return token
? {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
}
: { headers }
})
const httpLink = new HttpLink(config)
const link = ApolloLink.from([/* retryLink, */ authLink, httpLink])
const cache = new InMemoryCache()
// overriding apolloClient in the global scope of this module
const apolloClient = new ApolloClient({
link,
cache
})
/* END */
apolloClient.query({ query: User.self, forceFetch: true })
.then(authneticatedUser => {
console.trace('response', authneticatedUser)
if(authneticatedUser.data.user === null)
throw ('no user')
apolloClient is configured from apollo-client not apollo-boost. It is attached to its provider in App.js:
return (
<ApolloProvider client={this.state.apolloClient}>
that is loaded from a different file with getApolloClient() -- which sets a local variable apolloClient:
var apolloClient //...
export const getApolloClient = () => { // ...
apolloClient = new ApolloClient({
link,
cache
}) //...
return apolloClient
all calls to .query or .mutate are done from exported functions in this same file, and they use that same var apolloClient. I do not ever instantiate more than one apollo-client. Why is it that some of my queries are firing the middleware, but others are not ?
edit:
per request, the actual links used:
// from src: https://github.com/kadikraman/offline-first-mobile-example/blob/master/app/src/config/getApolloClient.js
export const getApolloClient = async () => {
const retryLink = new RetryLink({
delay: {
initial: 1000
},
attempts: {
max: 1000,
retryIf: (error, _operation) => {
if (error.message === 'Network request failed') {
//if (_operation.operationName === 'createPost')
// return true
}
return false
}
}
})
// from: https://www.apollographql.com/docs/react/recipes/authentication.html
const authLink = setContext(async (req, { headers }) => {
// get the authentication token from local storage if it exists
let getToken = async () => await AsyncStorage.getItem(/*'access_token'*/'graphcool_token')
const token = await getToken()
console.trace('token for connection to graphcool is currently', token, req.operationName)
// return the headers to the context so httpLink can read them
return token
? {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
}
: { headers }
})
const httpLink = new HttpLink(config)
const link = ApolloLink.from([retryLink, authLink, httpLink])
const cache = new InMemoryCache()
apolloClient = new ApolloClient({
link,
cache
})
try {
await persistCache({
cache,
storage: AsyncStorage
})
} catch (err) {
console.error('Error restoring Apollo cache', err) // eslint-disable-line no-console
}
return apolloClient
}
It turns out that the problem has something to do with cache -- this section in the getApolloClient method:
try {
await persistCache({
cache,
storage: AsyncStorage
})
} catch (err) {
console.error('Error restoring Apollo cache', err) // eslint-disable-line no-console
}
It works if I change the code to save apolloClient before that change is applied to the copy sent to ApolloProvider, like this:
export var apolloClient
// from src: https://github.com/kadikraman/offline-first-mobile-example/blob/master/app/src/config/getApolloClient.js
export const getApolloClient = async () => {
apolloClient = await getRawClient()
try {
await persistCache({
cache,
storage: AsyncStorage
})
} catch (err) {
console.error('Error restoring Apollo cache', err) // eslint-disable-line no-console
}
return apolloClient
}
export const getRawClient = async () => {
const retryLink = new RetryLink({
delay: {
initial: 1000
},
attempts: {
max: 1000,
retryIf: (error, _operation) => {
if (error.message === 'Network request failed') {
//if (_operation.operationName === 'createPost')
// return true
}
return false
}
}
})
// from: https://www.apollographql.com/docs/react/recipes/authentication.html
const authLink = setContext(async (req, { headers }) => {
// get the authentication token from local storage if it exists
let getToken = async () => await AsyncStorage.getItem(/*'access_token'*/'graphcool_token')
const token = await getToken()
console.trace('token for connection to graphcool is currently', token, req.operationName)
// return the headers to the context so httpLink can read them
return token
? {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
}
: { headers }
})
const httpLink = new HttpLink(config)
const link = ApolloLink.from([/* retryLink, */ authLink, httpLink])
const cache = new InMemoryCache()
return new ApolloClient({
link,
cache
})
}
Then, I also refactor the query & mutate code out of this file, importing apolloClient. That works... which is kinda bizarre, but whatever.