First of all it is a similar question like How to properly reuse connection to Mongodb across NodeJs application and modules, but I guess due to the ES6 syntax it's still different.
So I decided to use MongoDriver and created a class for this like in SO answer: dbconnections.js
import { default as mongodb } from 'mongodb';
const MongoClient = mongodb.MongoClient;
const url = "myurl"
let _db;
export const connectToServer = async (callback) => {
try {
MongoClient.connect( url, { useNewUrlParser: true, useUnifiedTopology:true }, ( err, db ) => {
_db = db
return callback( err )
})
} catch (e) {
throw e
}
}
export const getDB = () => _db
export const disconnectDB = () => _db.close()
The problem with this module is, that due to the ES6 syntax is that I can't make like something this
import {getDB} from '../dbconnections.js'
const driverDB=getDB()
export const someFunction= async (req,res) => {
console.log(driverDB)
because I always get undefiened so I have to call my getter in every function.
So my question: how to create properly a structure for a MongoDriver handler and pass this to different modules with ES6?
Pass the client object into objects and functions that need it.
https://en.wikipedia.org/wiki/Dependency_injection
Related
I try to use a brand new feature released in NextJS v.12.1 https://deepinder.me/nextjs-on-demand-isr. The API itself works fine. I can reach it. But in exchange it returns 500 error that says res.unstable_revalidate is not a function. It does not work either over dev (next server && next dev) run or production one (next build && next start).
This is the api endpoint:
// ./api/misc/revalidate
const revalidateCache = async (_, res) => {
console.log(res.unstable_revalidate, 'TEST REVALIDATE'); // res.unstable_revalidate is undefined here :(
try {
await res.unstable_revalidate('/');
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send(`Error revalidating: ${err}`);
}
};
export default revalidateCache;
This is the invoke:
// ./apps/client/services/server
const getRevalidate = async () => {
await fetch('/api/misc/revalidate');
};
export default getRevalidate;
View layer that I call from:
// .src/pages/index.js
// ...some code here
const HomePage = ({ page, legacy }) => {
const handleClick = () => {
getRevalidate();
};
return (
<div className={styles.homeRoot}>
<button onClick={handleClick}>REVALIDATE</button>
</div>
);
};
UPD:
I use express to handle API abstration.
import express from 'express';
import revalidateCacheApi from './api/misc/revalidate';
export default app => {
// ...some code here
app.use('/api/misc/revalidate', revalidateCacheApi);
};
NVM. It was an issue with my local server. I use advanced set up with two independent instances (:3000 and :4000) spinning in the memory.
The way I designed API above suppose to call it over :4000 server. Which is in fact Express server (obviously does not has NextJS internal API to purge the cache).
So I moved the call to the pages/api/revalidate and up to :3000 server.
Works fine:
// ./src/pages/server/revalidate.js
const getRevalidate = async () => {
await fetch('/api/revalidate');
};
export default getRevalidate;
Is there a recommended pattern in Remix for running common code on every request, and potentially adding context data to the request? Like a middleware? A usecase for this might be to do logging or auth, for example.
The one thing I've seen that seems similar to this is loader context via the getLoadContext API. This lets you populate a context object which is passed as an arg to all route loaders.
It does work, and initially seems like the way to do this, but the docs for it say...
It's a way to bridge the gap between the adapter's request/response API with your Remix app
This API is an escape hatch, it’s uncommon to need it
...which makes me think otherwise, because
This API is explicitly for custom integrations with the server runtime. But it doesn't seem like middlewares should be specific to the server runtime - they should just be part of the 'application' level as a Remix feature.
Running middlewares is a pretty common pattern in web frameworks!
So, does Remix have any better pattern for middleware that runs before every loader?
Instead of middleware, you can call a function directly inside the loader, this will also be more explicit. If you want to early return a response from those "middlewares" Remix let you throw the response object.
For example, if you wanted to check the user has a certain role you could create this function:
async function verifyUserRole(request: Request, expectedRole: string) {
let user = await getAuthenticatedUser(request); // somehow get the user
if (user.role === expectedRole) return user;
throw json({ message: "Forbidden" }, { status: 403 });
}
And in any loader call it this way:
let loader: LoaderFunction = async ({ request }) => {
let user = await verifyUserRole(request, "admin");
// code here will only run if user is an admin
// and you'll also get the user object at the same time
};
Another example could be to require HTTPS
function requireHTTPS(request: Request) {
let url = new URL(request.url);
if (url.protocol === "https:") return;
url.protocol = "https:";
throw redirect(url.toString());
}
let loader: LoaderFunction = async ({ request }) => {
await requireHTTPS(request);
// run your loader (or action) code here
};
There is no way inside Remix to run code before loaders.
As you found out, there is the loader context but it runs even before remix starts to do its job (so you won't know which route modules are matched for example).
You can also run arbitrary code before handing the request to remix in the JS file where you use the adapter for the platform you're deploying to (this depend on the starter you used. This file doesn't exist if you've chosen remix server as your server)
For now it should work for some use cases, but I agree this is a missing feature in remix for now.
Inside app/root.tsx
export let loader: LoaderFunction = ({ request }) => {
const url = new URL(request.url);
const hostname = url.hostname;
const proto = request.headers.get("X-Forwarded-Proto") ?? url.protocol;
url.host =
request.headers.get("X-Forwarded-Host") ??
request.headers.get("host") ??
url.host;
url.protocol = "https:";
if (proto === "http" && hostname !== "localhost") {
return redirect(url.toString(), {
headers: {
"X-Forwarded-Proto": "https",
},
});
}
return {};
};
Source: https://github.com/remix-run/remix-jokes/blob/8f786d9d7fa7ea62203e87c1e0bdaa9bda3b28af/app/root.tsx#L25-L46
here is my middlewares implementation for remix with typescript,it's works well
ctx.return(something)=== useLoaderData()
import compose from '#utils/compose';
export default function Index() {
const ctx = useLoaderData();
return <div>{ctx.name}</div>;
}
type DefaultCtx = {
name: string;
} & Request;
export const loader =(...args)=>compose<DefaultCtx>(
async (ctx, next) => {
ctx.name = 'first';
await next();
},
async (ctx, next) => {
ctx.name = 'secnod';
await next();
},
async (ctx, next) => {
ctx.name = 'third';
ctx.return(ctx);
await next();
}
)(args);
compose is same as koa;
here is the compose's implementation
type Next = () => Promise<void>;
type Context = {};
type Middle<T = {}> = (ctx: Context & T, next: Next) => void;
const compose = <T>(...middlewares: Middle<T>[]) => {
return middlewares.reverse().reduce(
(dispatch, middleware) => {
return async ctx =>
middleware(ctx, async () => dispatch(ctx, async () => {}));
},
async () => {}
);
};
export type Middleware<T = {}, P = unknown> = (
ctx: Context & T & { return: (param: P) => void },
next: Next
) => void;
const returnEarly: Middleware = async (ctx, next) => {
return new Promise<any>(async resolve => {
ctx.return = resolve;
await next();
});
};
const componseWithReturn = <T>(...middlewares: Middleware<T>[]) =>
compose(returnEarly, ...middlewares) as (ctx: T) => void;
export default componseWithReturn;
Here is the scenario:
I have 3 files (modules):
app.js
(async () => {
await connectoDB();
let newRec = new userModel({
...someprops
});
await newRec.save();
})();
The app.ts is the entry point of the project.
database.ts
interface ConnectionInterface {
[name: string]: mongoose.Connection;
}
export class Connection {
public static connections: ConnectionInterface;
public static async setConnection(name: string, connection: mongoose.Connection) {
Connection.connections = {
...Connection.connections,
[name]: connection,
};
}
}
export async function connectToDB() {
const conn = await mongoose.createConnection('somePath');
await Connection.setConnection('report', conn);
}
model.ts
const userSchema = new mongoose.Schema(
{
..someprops
},
);
const userModel = Connection.connections.report.model('User', userSchema);
export default userModel;
What I am trying to do: I need to have multiple mongoose connections, so I use an static prop called connections in Connection class (in database.ts); every time that I connect to a database I use setConnection to store the connection in mentioned static prop, so I can access it from every module in my project by its name which is report in this case.
Later, In model.ts I use Connection.connections.report to access the connection report to load my model!
Then, When I run app.ts I get the following error which is logical:
const aggregationModel = Connection.connections.report.model('User', userSchema)
^
TypeError: Cannot read property 'report' of undefined
The reason that causes this (I think) is, while loading imported modules in app.ts, .report is not declared because the app.ts isn't run completely (connectoDB() defines the .report key).
The codes that I have mentioned have been simplified for preventing complexity. The original app is an express app!
Now, How should I solve this error?
Thanks in advance.
You can wait for the connection to finish before using it if you change up your class slightly.
const connection = await Connection.getConnection()
const model = connection.example
...
class Connection {
...
public static async getConnection() => {
if (!Connection.connection) {
await Connection.setConnection()
}
return Connection.connection
}
}
I'm trying to test my controller (express middleware) with Jest. In order to explain my problem, I'll provide my code:
import request from 'utils/request';
import logger from 'config/logger';
const get = async (req, res, next) => {
try {
const response = await request.get('entries?content_type=category');
return res.json(response.data);
} catch (error) {
logger.error(error.response.data.message);
return next(error);
}
};
module.exports = {
get,
};
I need to test this get function. In order to do so, I need to provide the req, res and next args to it. I've found this question where the op talks about mocking the express request and then says "how to use it" but I can't see how. That's the only topic that I've seen so far directly related with my problem but the answer doesn't work for me (I don't want to use Nock nor any other library to adchieve the testing, just Jest).
So, how can I successfully test this function? Is it by using mocking or is there any other way?
Sorry for my bad english and thanks in advance for your help!
If you are writing unit tests, then mocking is the more appropriate way to go. using Jest, Mocking should be available out of the box. In a test file, it may look something like this:
import request from 'utils/request';
import logger from 'config/logger';
import { get } from 'middleware/get'; // Or whatever file you are testing
jest.mock('request'); // Mocking for a module import
jest.mock('logger'); // Mocking for a module import
const mockReq = () => {
const req = {};
// ...from here assign what properties you need on a req to test with
return req;
};
const mockRes = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
test('should return a json response of data', () => {
const mockedNext = jest.fn();
const mockedReq = mockReq();
const mockedRes = mockRes();
const mockedEntries = {
data: {}
};/*...whatever mocked response you want back from your request*/
request.get.mockResolvedValue(mockedEntries);
const result = get(mockedReq, mockedRes, mockedNext);
expect(result).to.equal(mockedEntires.data);
expect(mockedNext.mock.calls.length).toBe(1);
expect(mockedRest.json).toHaveBeenCalledWith(mockedRes.data)
});
Jest Mocking
I'm using knexnest as follows:
const customerProducts = knex.select([
'CustomerProduct.id AS _id',
'CustomerProduct.price AS _price',
'CustomerProduct.active AS _active',
'CustomerProduct.customer_id AS _customerId',
'CustomerProduct.product_id AS _productId',
'customer.id AS _customer_id',
'customer.name AS _customer_name',
])
.from('customer_products AS CustomerProduct')
.innerJoin(
'customers AS customer',
'CustomerProduct.customer_id',
'customer.id',
)
.where(whereClause);
const result = knexnest(customerProducts).then(data => data);
return result;
I'll likely be creating several other queries with which I'll then use knexnest to create a nested model. Is there a way that I can pull knexnest(customerProducts).then(data => data); out to a different file so that I could just call it rather than adding that line with every query and also having to import knexnest into every file?
To achieve what you're trying to accomplish, the simplest way would be to export a function that returns the promise generated by knexnest. Then, import that function in the main file and call it. You can either use async/await syntactic sugar or a .then. It's important that you use one knex connection object throughout your application to leverage knex's connection pool. Therefore, we must pass it into your function so the other file can access the established connection object.
File 1
const Knex = require('knex');
// import the function getCustomerProducts using destructuring
const { getCustomerProducts } = require('<File 2's Path>');
// initialize knex & set up knex database connection
const knex = Knex({
client: 'postgres',
connection: process.env.DATABASE_URL
});
// pseudo-code - note: await can only be called within an async function
// pass knex connection object into function to leverage knex's connection pooling
const products = await getCustomerProducts(knex);
File 2
const knexnest = require('knexnest');
const getCustomerProducts = knex => {
const customerProducts = knex.select([
'CustomerProduct.id AS _id',
'CustomerProduct.price AS _price',
'CustomerProduct.active AS _active',
'CustomerProduct.customer_id AS _customerId',
'CustomerProduct.product_id AS _productId',
'customer.id AS _customer_id',
'customer.name AS _customer_name',
])
.from('customer_products AS CustomerProduct')
.innerJoin(
'customers AS customer',
'CustomerProduct.customer_id',
'customer.id',
)
.where(whereClause);
return knexnest(customerProducts);
};
// named export of the function getCustomerProducts
module.exports.getCustomerProducts = getCustomerProducts;