Introduction
Having an interactive REPL is one of those things that makes experimenting and debugging so much nicer. Django has it built-in, through the manage.py shell command. This is basically the Python REPL with Django context preloaded. Express does not have anything like that built-in, but it is easy to implement it.
All code snippets shown will reference this tutorial project.
TL;DR
# javascript
node --interactive --eval "`cat init-context.js`"
# typescript
ts-node --interactive --eval "`cat init-context.js`"
Programmatically
The approach I’ve seen the most online, for example in this article, is to instantiate the REPL programmatically, using the repl module. Following the aforementioned article, we may start up a Javascript REPL using the following script.
#!/usr/bin/env node
const repl = require("repl");
const mongoose = require("mongoose");
// app and configuration
const app = require("./app.js");
// Mongoose models
const Author = require("./models/author.js");
const Book = require("./models/book.js");
const BookInstance = require("./models/bookinstance.js");
const Genre = require("./models/genre.js");
const mongoDB = "mongodb://localhost:27017/local_library";
async function main() {
await mongoose.connect(mongoDB);
process.stdout.write("Database and Express app initialized.\n");
const r = repl.start();
// set context
r.context.app = app;
r.context.Author = Author;
r.context.Book = Book;
r.context.BookInstance = BookInstance;
r.context.Genre = Genre;
r.on("exit", () => {
process.exit();
});
r.setupHistory(".shell_history", () => {});
}
main().catch(console.error);
It took me some time to understand how to start a Typescript REPL instead. This is possible using the repl and ts-node modules combined, as shown below.
#!/usr/bin/env node
const repl = require("repl");
const tsnode = require("ts-node");
const mongoose = require("mongoose");
// app and configuration
const app = require("./app.js");
// Mongoose models
const Author = require("./models/author.js");
const Book = require("./models/book.js");
const BookInstance = require("./models/bookinstance.js");
const Genre = require("./models/genre.js");
const mongoDB = "mongodb://localhost:27017/local_library";
async function main() {
await mongoose.connect(mongoDB);
process.stdout.write("Database and Express app initialized.\n");
const tsrepl = tsnode.createRepl();
const service = tsnode.create({ ...tsrepl.evalAwarePartialHost });
tsrepl.setService(service);
const r = repl.start({ eval: tsrepl.nodeEval });
// set context
r.context.app = app;
r.context.Author = Author;
r.context.Book = Book;
r.context.BookInstance = BookInstance;
r.context.Genre = Genre;
r.on("exit", () => {
process.exit();
});
r.setupHistory(".shell_history", () => {});
}
main().catch(console.error);
Leveraging the command line
While fidgeting with this last script I realized there is a much simpler way that leverages the standard REPL executables: node and ts-node.
This approach mocks the -i option of the python REPL. When this option is given, the REPL will execute the script (or the command if -c is used) and then enter interactive mode.
node and ts-node do not allow this kind of behavior, so if we try to do something like node -i init-context.js the REPL will just hang. What we can do is to evaluate the content of the file and enter interactive mode, as follows.
# javascript
node -i -e "`cat init-context.js`"
# typescript
ts-node -i -e "`cat init-context.js`"
// init-context.js
console.log('Initializing db and express app...');
const mongoose = require("mongoose");
// app and configuration
const app = require('./app.js');
// Mongoose models
const Author = require('./models/author.js');
const Book = require('./models/book.js');
const BookInstance = require('./models/bookinstance.js');
const Genre = require('./models/genre.js');
mongoose.connect("mongodb://localhost:27017/local_library")
.catch(console.error);
We can define helper scripts, so that we can launch a shell with npm run shell.
// package.json
"scripts": {
"shell": "node -i -e \"`cat init-context.js`\"",
"ts-shell": "npx ts-node -i -e \"`cat init-context.js`\""
},
Conclusions
This works nicely and it is not bound in any way to express. It is rather a way of initializing your REPL context.