Using Handlebars with Deno v1.6.x - javascript

I'm struggling to get the Handlebars templating working with Deno v1.6.x, any thoughts?
$ deno --version
deno 1.6.2 (release, x86_64-unknown-linux-gnu)
v8 8.8.278.2
typescript 4.1.3
$ tree
.
├── template.js
└── views
├── layouts
│ └── main.hbs
└── partials
/* template.js */
import { Application, Router, Status } from 'https://deno.land/x/oak/mod.ts'
import { Handlebars } from 'https://deno.land/x/handlebars/mod.ts'
const port = 8080
const app = new Application()
const router = new Router()
const handle = new Handlebars()
// error handler
app.use(async (context, next) => {
try {
await next()
} catch (err) {
console.log(err)
}
})
// the routes defined here
router.get('/', async context => {
const data = {
title: 'Fools Day',
date: '01/04/20'
}
context.response.body = await handle.renderView('main', data)
})
app.use(router.routes())
app.use(router.allowedMethods())
// static content
app.use(async (context, next) => {
const root = `${Deno.cwd()}/static`
try {
await context.send({ root })
} catch {
next()
}
})
// page not found
app.use( async context => {
context.response.status = Status.NotFound
context.response.body = `"${context.request.url}" not found`
})
app.addEventListener("listen", ({ port }) => console.log(`listening on port: ${port}`) )
await app.listen({ port })
I get the following error when I try to view the page:
$ deno run --allow-net --unstable template.js
Check file:///home/xxx/template.js
listening on port: 8080
NotFound: No such file or directory (os error 2)
at processResponse (deno:core/core.js:223:11)
at Object.jsonOpAsync (deno:core/core.js:240:12)
at async open (deno:runtime/js/30_files.js:44:17)
at async readFile (deno:runtime/js/40_read_file.js:15:18)
at async Handlebars.render (mod.ts:98:53)
at async Handlebars.renderView (mod.ts:76:26)
at async file:///home/xxx/template.js:28:26
at async dispatch (middleware.ts:41:7)
at async dispatch (middleware.ts:41:7)
at async dispatch (middleware.ts:41:7)
I've looked at the documentation at https://deno.land/x/handlebars#v0.6.0 and think I have used all the default settings but the error message doesn't tell me what file is not found.
Has anyone had experience using Handlebars with Deno?
Cheers.

EDIT: Make sure you have a layout file according to your default layout!!
If you do not want a default layout file then you can do this:
const handle = new Handlebars({defaultLayout:''});
Here's the code for renderView
public async renderView(
view: string,
context?: Record<string, unknown>,
layout?: string,
): Promise<string> {
if (!view) {
console.warn("View is null");
return "";
}
const config: HandlebarsConfig = this.config as HandlebarsConfig;
const partialsPathes = await this.getTemplatesPath(
join(config.baseDir, config.partialsDir),
);
partialsPathes && (await this.registerPartials(partialsPathes));
const path = join(config.baseDir, view + config.extname);
const body: string = await this.render(path, context);
layout = (layout as string) || config.defaultLayout;
if (layout) {
const layoutPath: string = join(
config.baseDir,
config.layoutsDir,
layout + config.extname,
);
return this.render(layoutPath, { ...context, body });
}
return body;
}
The first parameter, in your case main tells the code to look for a file called main.hbs in your views directory (provided you are using the default config).
You can additionally pass a layout parameter to render it within a layout file that is housed in your layouts directory.

Related

NodeJS: Controlling the order of loading modules

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
}
}

Error occurred prerendering page in Next JS [duplicate]

This question already has an answer here:
Fetch error when building Next.js static website in production
(1 answer)
Closed last year.
I created an API in next JS (in the pages/api folder) and I used it on a page in the pages folder.
When I run on the localhost (development stage), the API can be called correctly. But when I deploy to Vercel there is an error during build.
This is my code when i call the API which is in the pages/api folder
export const getStaticProps = async () => {
const baseUrlDribble = 'https://api.dribbble.com/v2';
const baseUrl = process.env.NODE_ENV === 'production' ?
'https://jovanka-samudra.vercel.app/api' : 'http://localhost:3000/api';
const resShots = await fetch(`${baseUrlDribble}/user/shots?access_token=${process.env.TOKEN_DRIBBLE}&page=1&per_page=9`);
const shots = await resShots.json();
const resResult = await fetch(`${baseUrl}/projects`);
const result = await resResult.json();
const projects = result.data.projects;
return {
props: {
shots,
projects,
},
revalidate: 1,
}
}
This is the API code to retrieve data from database (pages/api/projects folder)
import ProjectService from "#services/ProjectService";
import connectDB from "#utils/connectDB";
import projectValidator from "#validators/project";
import ClientError from '#exceptions/ClientError';
const handler = async (req, res) => {
const projectService = new ProjectService();
if (req.method === 'GET') {
try {
const projects = await projectService.getProjects();
return res.status(200).json({
success: true,
length: projects.length,
data: {
projects
}
});
} catch (error) {
return res.status(500).json({
success: false,
message: error.message,
});
}
} else if (req.method === 'POST') {
...
}
return res.status(404).json({
success: false,
message: 'Method not alowed'
});
}
export default connectDB(handler);
services/ProjectService folder
import InvariantError from '#exceptions/InvariantError';
import NotFoundError from '#exceptions/NotFoundError';
import Project from '#models/Project';
class ProjectService {
async getProjects() {
const projects = await Project.find().sort({ 'createdAt': -1 });
return projects;
}
...
}
export default ProjectService;
You should not fetch an internal API route from getStaticProps — instead, you can write the fetch code in API route directly in getStaticProps.
https://nextjs.org/docs/basic-features/data-fetching/get-static-props#write-server-side-code-directly

Cannot read from bundled Database

I bundle a pre-populated sqlite-database in the asset/sqlite/ folder of my project. I edited the metro.config.js in my root folder of the app like this
const { getDefaultConfig } = require('#expo/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
module.exports = {
resolver: {
assetExts: [...defaultConfig.resolver.assetExts, 'db'],
},
};
I then try to read the database like this
import * as SQLite from 'expo-sqlite';
import * as FileSystem from 'expo-file-system';
import { Asset } from 'expo-asset';
async function openDatabase() {
// check if folder exists
if (!(await FileSystem.getInfoAsync(FileSystem.documentDirectory + 'sqlite')).exists) {
// if folder does not exist, create it
await FileSystem.makeDirectoryAsync(FileSystem.documentDirectory + 'sqlite');
}
await FileSystem.downloadAsync(
// grab database from asset folder
Asset.fromModule(require('../../../assets/sqlite/foo.db')).uri,
// move to new folder for application to work with it
FileSystem.documentDirectory + 'sqlite/foo.db'
)
return SQLite.openDatabase('foo.db');
}
export function savePoints(location) {
...
const somedb = openDatabase();
somedb.transaction(tx => {...}
}
But that gives me the following
[Unhandled promise rejection: Error: Directory for 'file:///Users/.../Library/Developer/CoreSimulator/Devices/6CD71445-E39B-430A-9691-B174D6300E9A/data/Containers/Data/Application/B543CDCB-6D7E-47D2-ABDD-411FCE115C4C/Documents/ExponentExperienceData/%2540anonymous%252Fmapstar-b773f7ad-9cee-4848-8e06-82f7ca69effc//sqlite/foo.db' doesn't exist.
Please make sure directory '/Users/.../Library/Developer/CoreSimulator/Devices/6CD71445-E39B-430A-9691-B174D6300E9A/data/Containers/Data/Application/B543CDCB-6D7E-47D2-ABDD-411FCE115C4C/Documents/ExponentExperienceData/%40anonymous%2Fmapstar-b773f7ad-9cee-4848-8e06-82f7ca69effc/sqlite' exists before calling downloadAsync.]
and
[Unhandled promise rejection: TypeError: undefined is not a function (near '...foo.db.transaction...')]
Why is that? The database exists, the path is correct too.
Because openDatabase() is an async function, the result must be awaited.
Instead of
const somedb = openDatabase();
somedb.transaction(tx => {...}
do
somedb.then(db => {...}

How to serve static file if exists, and a file with default values if not exists with koa.js

I want to modify a framework. At the moment, it creates a robots.txt file with default values. It should check first, if robots.txt exists, and if not, create it as before.
The code looks like this at the moment:
import Koa from "koa";
import { get } from "koa-route";
import serve from "koa-static";
import mount from "koa-mount";
import React from "react";
import { Context } from "#frontity/types";
export default ({ packages }): ReturnType<Koa["callback"]> => {
const app = new Koa();
// Serve static files.
app.use(mount("/static", serve("./build/static")));
// Default robots.txt.
app.use(
get("/robots.txt", (ctx) => {
ctx.type = "text/plain";
ctx.body = "User-agent: *\nDisallow:";
})
);
// Ignore HMR if not in dev mode or old browser open.
const return404 = (ctx: Context) => {
ctx.status = 404;
};
app.use(get("/__webpack_hmr", return404));
app.use(get("/static/([a-z0-9]+\\.hot-update\\.json)", return404));
// Return Frontity favicon for favicon.ico.
app.use(get("/favicon.ico", serve("./")));
// Frontity server rendering.
app.use(async (ctx, next) => {
...
});
return app.callback();
};
I could serve it like favicon.ico is served: app.use(get("/robots.txt", serve("./")));, but I have no idea, how to check it first, if the file exists, and if not return the default value:
(ctx) => {
ctx.type = "text/plain";
ctx.body = "User-agent: *\nDisallow:";
})
I check the file existence with fs.existsSync, like:
import fs from "fs";
let hasRobotTxt = false;
if (fs.existsSync("./robots.txt")) {
hasRobotTxt = true;
}
Then serve it conditionally like:
app.use(
get(
"/robots.txt",
hasRobotTxt
? serve("./")
: (ctx) => {
ctx.type = "text/plain";
ctx.body = "User-agent: *\nDisallow:";
}
)
);

Conditional url based on NODE_ENV on Next.js

Is there a way for me to set an url based on whether I'm in development or production?
Currently I have a component with the following code:
export default class Search extends Component {
static async getInitialProps({ query: { location } }) {
const res = await fetch(
`http://localhost:3000/api/search?location=${location}`
)
const businesses = await res.json()
return businesses
}
...
}
I would like something that allows me to do the following:
export default class Search extends Component {
static async getInitialProps({ query: { location } }) {
let res
if (environment is in developement) {
res = await fetch(
`http://localhost:3000/api/search?location=${location}`
)
} else if (environment is in production) {
res = await fetch (
`https://productionurl.now.sh/api/search?location=${location}`
)
}
const businesses = await res.json()
return businesses
}
...
}
You can do that using the NODE_ENV environment variable. For a nice developer experience, set up a config file like this:
/config/index.js
const dev = process.env.NODE_ENV !== 'production';
export const server = dev ? 'http://localhost:3000/api' : 'https://productionurl.now.sh/api';
Then you can use that inside your getInitialProps methods throughout your application.
/components/Search.js
import { server } from '../config';
// ...
static async getInitialProps({ query: { location } }) {
const res = await fetch(`${server}/search?location=${location}`);
const businesses = await res.json();
return businesses;
}
Make sure that the NODE_ENV variable is set inside package.json build scripts, which should look something like this.
package.json
"scripts": {
"build": "NODE_ENV=production next build",
},
Here's an example on how to setup development and production
const prodConfig = {
publicRuntimeConfig: {
API_ENDPOINT: 'http://google.com/api'
}
}
const devConfig = {
publicRuntimeConfig: {
API_ENDPOINT: 'http://localhost:3000/api'
}
}
module.exports = process.env.NODE_ENV === 'production ? prodConfig : devConfig
Yes, like what alex bennett has commented, using dotenv should work for your case!
To set it up,
Install dotenv as a dependency on your Node.js project npm install dotenv --save then require it in your application require('dotenv').config()
Create a file called .env in the root directory of your project with the environment variables that you need in this <NAME>/<VALUE> format here: MY_ENVIRONMENT=production.
Change <VALUE> to production if you're deploying from your hosted server, or to development if you're deploying from your localhost.
When that's all set up, you can very easily check the loaded environment variables in your code like this (from your example):
export default class Search extends Component {
static async getInitialProps({ query: { location } }) {
let res
if (process.env.MY_ENVIRONMENT === 'development') {
res = await fetch(
`http://localhost:3000/api/search?location=${location}`
)
} else if (process.env.MY_ENVIRONMENT === 'production') {
res = await fetch (
`https://productionurl.now.sh/api/search?location=${location}`
)
}
const businesses = await res.json()
return businesses
}
...
}

Categories