I'm trying to build an express server.
In the app.js file I create the server and try to connect to my mongodb.
import express from "express";
import dbo from "./db/conn.js";
const app = express();
app.listen(PORT, async () => {
await dbo.connectToDatabase();
log.info(`Server is running on port: ${PORT}`);
});
In file conn.js
const client = new MongoClient(uri);
let db: Db;
export async function connectToDatabase() {
try {
await client.connect();
db = client.db(dbName);
await db.command({ ping: 1 });
log.info(`Successfully connected to database: ${db.databaseName}.`);
} finally {
await client.close();
}
}
export async function getDb() {
return db;
}
export default { connectToDatabase, getDb };
And now in a seperate file, I wantt to use the database connection I created in the app.js file.
like this:
const collection = getDb().collection(collectionName);
// insert documents in collection.
but when I try to run the app I get error TypeError: Cannot read properties of undefined.
So, is there a way to wait until the db connection is established?
There are some problems with your code.
First, the getDb function returns promise. So you can't use the collection method right away. Remove the async keyword because the current getDb function is not an asynchronous function.
export function getDb() {
return db;
}
Second, in the try & finally statement, the finally block is executed unconditionally regardless of whether an exception occurs in the code. Therefore, after displaying the connection success log in the connectToDatabase, the connection closes immediately. In order to insert the data, the connection must be connected, so please erase the await client.close(); code in finally block from connectToDatabase.
Then you can add a document to the collection without a problem.
One way to accomplish what you're trying to do is to pass that connection to a class or function you define elsewhere.
Say this is your collection code (or whatever thing even outside a collection that needs a raw db connection instance in order to use):
class Collection {
constructor(db) {
this.db = db
//do whatever else needs to be setup
}
use(name) {
//or use it here
this.db.use(name)
}
}
export default Collection
Then in your main file some code:
import Collection from './Collection.js'
const collection = new Collection(getDb)
collection.use('some arbitrary name')
This is just one way to do it but there are other ways as well. Also if this is the only thing using your getDb() method above, you can remove it from your main and put it in here as a manager.
Related
I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.
I am trying to implement route protection using the new Next.js 12 middleware function. But it occurs to me that every time I try to access the session I get null. Hence not getting the expected result. Why is that?
import { NextResponse, NextRequest } from 'next/server'
import { getSession } from "next-auth/react"
//This acts like a middleware, will run every time for pages under /dashboard, also runs for the nested pages
const redirectUnauthenticatedUserMiddleware = async (req, ev) => {
const session = await getSession({ req })
if (!session) {
return NextResponse.redirect('/login')
}
return NextResponse.next()
}
export default redirectUnauthenticatedUserMiddleware
There's another ways on how you can get access to your session.
Here we're using getToken from next-auth/jwt.
to note:
must use or declared secret as an option in NextAuth function (related reference here). If not, it will not work. Doesn't matter if you use session or jwt option. Both will work.
export default NextAuth({
secret: process.env.SECRET,
...otherOptions
})
currently I don't know whether it will work or not if you don't use token. Or you use database option only. Or adapter. I'm not sure since currently I'm unable to make adapter work just yet. So that's that.
Here is an example _middleware.js:-
import { getToken } from 'next-auth/jwt';
import { NextResponse } from 'next/server';
export async function middleware(req, ev) {
// 'secret' should be the same 'process.env.SECRET' use in NextAuth function
const session = await getToken({ req: req, secret: process.env.SECRET }); console.log('session in middleware: ', session)
if(!session) return NextResponse.redirect('/')
return NextResponse.next()
}
If you already found a solution on how to make use getSession. You may upload your answer and share with us. Thank you.
I am currently trying to add a service layer to my NodeJS project, which is a simple API I build using express and sequelize. The project has a structure of the usual model, route, and controller (MRC) form; each has its own directory. Now all I need to do is add another directory that will contain the service files. The files will contain the business logic in the form of a class that I will export and include in my controller files.
Here I will display the controller and service files:
eruptionController.js
const EruptionService = require('../services/eruptionService.js');
let Eruption = new EruptionService()
module.exports = {
getEruptions: (req, res) => {
Eruption.get().then(result => {
return res.status(200).json({
message: "Get All Eruptions",
result
})
}).catch(err => {
return res.status(200).json({
message: "internal Server Error",
error: err.message
})
})
}
};
eruptionService.js
const { eruption } = require('../models');
class EruptionService {
constructor () {
this.eruptionModel = eruption;
}
get () {
this.eruptionModel.findAll()
.then(result => result)
.catch(err => err)
}
}
module.exports = EruptionService;
As we can see here, in the service file, I imported the eruption model from the model folder and include it in the constructor. Then I created a method named get, which purpose to retrieve all the eruption data from the database.
After that, I imported the EruptionService class into the controller file, initiated it as an instance, and include the class in the getEruptions function. The function will then be used in an express route in the form of an endpoint.
The issue is that when I tried to send a request to the endpoint, I got the following error
TypeError: Cannot read property 'then' of undefined
Based on the error, it seems that the EruptionService class was not exported properly since when I called the mothed get it's undefined.
I've been stuck with this for a while now, and I'm not really sure what's going on. Based on this post, I seem to be exporting the class properly.
I've also tried exporting in a way like below
module.exports = {EruptionService: EruptionService}
And I've also tried importing like
const EruptionService = require('../services/eruptionService.js');
But in the end, it either results in the same error or different one kind of error. I was wondering if anybody could point out what I was missing? Thanks in advance
You should return a promise function for getting the then function inside it. There is a issue in your service it should be like the below snippet
class EruptionService {
constructor () {
this.eruptionModel = eruption;
}
get () {
return this.eruptionModel.findAll()
}
}
Hello guys and thank you for your time.
I am testing with jest, using mongo memory server and mongoose schema.
One of my tests require me to use mockingoose, however from the point that i import mockingoose
(import mockingoose from 'mockingoose') every query that i do such as findOne directed to my mongo memory server will return the value of undefined.
code for example:
it('should work using mockingoose', () => {
mockingoose(userModel).toReturn(userDoc, 'findOne');
mockingoose(enterpriseMembersModel).toReturn([], 'aggregate');
mockingoose.resetAll();
//HERE i want to use mockingoose
}
it('should work using mongo memory server as usual', async () => {
const user = await userModel.findOne({_id: '5d303198550375179d5bb156'});
console.log(user);
//Return undefined for user.
//It will return an actual user only after removing mockingoose from the imports.
}
Is there any way to overcome this problem?
Thanks in advance!
I'm trying to figure out how to fit ajax calls into the flux/reflux way of doing things. From the scraps of info I've found online it seems correct to separate the API logic from the stores.
Starting there, I've created a client API that makes requests to the server. Now I want these functions them listen to my actions, then make a request, and upon success to trigger other actions that subsequently update the stores with the new data from the server.
How can go about having something similar to the reflux createStore method for my API? Or, is there some other clean way for my server calls to listen to actions?
The store is the glue between your actions and the API. Just like answered in https://stackoverflow.com/a/27487312/274483
The difference in your case is that you don't perform the ajax request directly in the store, but go through your API-class, populate the store with the data returned from your API and trigger an update of the store.
My Api is just a plain old javascript object (POJO) like this. It's in ES6 but you should get the idea:
import request from 'superagent';
// Other helpers functions and setup
let handle = (err) => {
// error handling stuff
};
export default {
create(user, cb) {
return request
.post(server + '/api/users/new')
.send(user)
.on('error', (err) => {
handle(err);
cb(err);
})
.end(cb);
},
login(user, cb) {
// Post some more stuff
}
};
Then, I call it in my Store like so:
import Reflux from 'reflux';
import UserActions from '../actions/UserActions';
import Api from '../api/UserApi';
const UserStore = Reflux.createStore({
listenables: [UserActions],
getInitialState() {
// insert stuff
},
onCreate(user) {
Api.create(user, (err, res) => {
if (err) {
console.log(err);
} else {
// Do something with res
// this is for JSON, your API might be different
let user = JSON.parse(res.text);
this.update(user);
}
})
},
onLogin(user) {
// login stuff
},
// More methods
update(user) {
this.currentUser = user;
this.trigger(user);
}
});
I don't update my store and this.trigger() until the api call returns.
A smarter idea might be to optimistically update:
// inside the todo store
onCreate(todo) {
Api.create(todo);
this.update([todos].concat(this.todos));
},
update(todos) {
this.todos = todos;
this.trigger(todos);
}
Obviously, this is one way to do it, but it's certainly not the only way.
But, the main idea is the Store uses the API.
The API is not part of the data flow of:
Action->Store->Component->Action etc.