Storing state in a Node/Express application - node.js

I'm writing a Node/Express app (my first) and need help persisting what feels like a global variable across page loads; but using a global variable feels wrong.
For the purposes of the question, the site is essentially 3 different table views on the same data. The data in the tables is taken from 3 separate MongoDB tables, merged together with a function, and displayed (using Pug, iterating over the object I just created).
The 3 different views are just different Pug templates iterating over the same object.
I have middleware which generates this object from MongoDB and stores it on res.locals, but I don't want to re-generate it by default every time the user selects a different view. The data hasn't changed, and this feels wasteful.
I want to be able to set some sort of variable, dataNeedsToBeUpdated, and if true then the function inside my middleware will actually do the work and regenerate the table; if false, it'll just skip the operation.
If the user performs one of my update operations, I'll set dataNeedsToBeUpdated = true, redirect, middleware will fire, and the data will refresh before the next page load.
How do I do this properly?

app.locals works just about like res.locals, just it's available to the whole app. You can access it in middleware from req.app.locals. The flow as you've described it sounds good; just use app.locals for storing the data and the dataNeedsToBeUpdated.

Related

Node access inside a table with testing library

I'm doing some testing to make sure that table data is rendered correctly in a table and in the right positions.
const tableRows = screen.getAllByRole("row");
expect(tableRows).toHaveLength(2); // Header plus one data row.
// Column 1 is a toggle icon.
expect(tableRows[1].querySelector("td:nth-child(2)").textContent).toBe("Hello");
expect(tableRows[1].querySelector("td:nth-child(3)").textContent).toBe("3 days ago");
expect(tableRows[1].querySelector("td:nth-child(4)").textContent).toBe("Success");
expect(tableRows[1].querySelector("td:nth-child(5)").textContent).toBe("EditDelete"); // Edit and Delete buttons.
However, es-lint is rightly complaining that I am using node access patterns via
Avoid direct Node access. Prefer using the methods from Testing Library. eslint(testing-library/no-node-access)
What's the best way to access nodes in this way?
Es-lint has some rules about this defined here
Depending on your render function, using Within may be an option for you. But for complex examples such as this we can make use of data-testid.
In the render function in the component, for each tableRow, you could add an attribute for data-testid and then access this element in the test through queryByTestId:
e.g. expect(screen.queryByTestId("table-row-2").textContent).toBe("Hello");

Saving a form part way through mongodb

I'm pretty new to MongoDB and best practices. I'm using Node full stack JS.
I created a form which spans multiple screens. This saves to a mongoose.model Schema and creates a document when the user submits the form.
I've been given the requirement to allow the user to save the form when part way through. The problem with trying to save to the existing Schema is I get a duplicate id reference error as I'm saving multiple fields as null which already exist in the Collection. Plus I'm thinking this is a waste of memory.
The answer is very simple it just took me some circular thinking to get to it. I check to see if properties are undefined before setting them. If they are undefined they are an empty value rather than null i.e. {} for an Object. Mongoose is ok with that, it just doesn't like null values (makes sense).

Best async practices for Jade template pages created from objects in mongodb

I have this website run on node.js using jade for page templates and mongodb to store some data. One of the pages is this 'parts' page. It is just a list of parts I use in some project, but that's not important.
So i have this scenario where:
I add an object in the form of a doc to the DB
That updates a global array of those objects
That global array gets passed to the jade template which draws the list for each element in the global array of objects.
On server startup, should the server ever restart, it populates the global by querying the DB once.
Here's my question,
Is this the best practice I can use in an async environment like node?
My rational is that i'm reducing the amount of time spent communicating with the DB by keeping the docs in the app.
Would it be better to query the DB for the docs and pass that to the template each time a user loads a page?
is using a global like this even async or just too fast to tell?
note: it's only for few records; say less then 200 records.
note: there will never be more than 1 person adding to the array at a time.
edit: third option, is this method bad all together?

How and where do you define your database structure in Meteor?

I am looking at the documentation for Meteor and it gives a few examples. I'm a bit confused about two things: First, where do you build the db (keeping security in mind)? Do I keep it all in the server/private folder to restrict client-side access? And second, how do I define the structure? For example, the code they show:
Rooms = new Meteor.Collection("rooms");
Messages = new Meteor.Collection("messages");
Parties = new Meteor.Collection("parties");
Rooms.insert({name: "Conference Room A"});
var myRooms = Rooms.find({}).fetch();
Messages.insert({text: "Hello world", room: myRooms[0]._id});
Parties.insert({name: "Super Bowl Party"});
I don't understand how a collection's structure is defined. Are they just able to define a collection and throw arbitrary data into it?
To answer your first question about where to put the new Meteor.Collection statements, they should go in a .js file in a folder accessible by both client and server, such as /collections. (With some exceptions: any collections that are never synced to the client, like server logs, should be defined inside /server somewhere; and any local collections should be defined in client code.)
As for your second question about structure: MongoDB is a document database, which by definition has no structure. Per the docs:
A database holds a set of collections. A collection holds a set of
documents. A document is a set of key-value pairs. Documents have
dynamic schema. Dynamic schema means that documents in the same
collection do not need to have the same set of fields or structure,
and common fields in a collection’s documents may hold different types
of data.
You may also have heard this called NoSQL. Each document (record in SQL parlance) can have different fields. Hence, there's no place where you define initial structure for a collection; each document gets its "structure" defined when it's inserted or updated.
In practice, I like to create a block comment above each new Meteor.Collection statement explaining what I intend the structure to be for most or all documents in that collection, so I have something to refer to later on when I insert or update the collection's documents. But it's up to me in those insert or update functions to follow whatever structure I define for myself.
A good practice would probably be defining your collection on both client and server with a single bit of javascript code. In other words, put the following
MyCollection = new Meteor.Collection("rooms");
// ...
anywhere but neither in the client nor in the server directory. Note that this directive alone does not expose any sensitive data to nobody.
A brand new meteor project would contain by default the insecure and autopublish packages. The former will basically allow any client to alter your database in every possible way, i.e. insert, update and remove documents. The latter will make sure that all database content is published to everyone, no matter how ridiculously this may sound. But fear not! Their only goal is to simplify the development process at the very early stage. You should get rid of these to guys from your project as soon as you start considering security issues of any kind.
As soon as the insecure package is removed from your project you can control the database privileges by defining MyCollection.allow and MyCollection.deny rules. Please check the documentation for more details. The only thing I would like to mention here is that this code should probably be considered as a sensitive one, so I guess you should put it into your server directory.
Removing the autopublish package has effect on the set of data that will be sent to your clients. Again you can control it and define privilages of your choice by implementing a custom Meteor.publish routine. This is all documented here. Here, you have no option. The code can only run in the server environment, so the best choice would be to put it in the server directory.
About your second question. The whole buzz about NoSQL databases (like mongodb) is to put as few restrictions on the structure of your database as possible. In other words, how the collections are structured is only up to you. You don't have to define no models and you can change the structure of your documents (and or remove fields) any time you want. Doesn't it sound great? :)

Referring to session-based data in a Mongoose virtual attribute

I have a Mongoose model that holds Places. Each place has a lat/lng. I can define a Mongoose virtual attribute called distance that would be used to sort Places in ascending order. What's the best way to refer to the user's location information (let's assume it's stored in a session variable for now) from inside the distance virtual attribute?
For anything involving external data, adding a method to the schema would be a better choice than a virtual property.
I'm solving a similar issue. The problem is that methods are fine if you want perform an operation on a single value but I'm retrieving a list and want to inject a new virtual field into every record in the list - but use session data to generate the field. to do this safely (avoiding globals), I think I'll need to use a QueryStream and inject the new field using an ArrayFormatter that takes the session variables as constructor parameters.
This also looks like a job for LINQ so another approach might be to use one of the ports of LINQ to JS.
If you sill prefer to use virtuals, you can store user location info in NodeJs globals. For example this code may be set after user login:
global.user_location = user.location;

Resources