Now and then I feel like it would be convenient to be able to post quick notes, or to quickly save bookmarks, here on my blog. The obvious IndieWeb solution would be to add Micropub support. Since I have a desire to learn more about the NestJS framework for creating NodeJS servers, it seems like creating a Micropub server using NestJS will be a nice personal project.
My first step was to get a better handle on creating a NestJS application, particularly using the Nx tool set that I have come to greatly appreciate at work. Some time spent doing Google searches on the subject lead me to Code-sharing made easy in a full-stack app with Nx, Angular, and NestJS, posted recently by Mateus Carniatto. Following along with the article gave me a good basis for how to structure an Nx monorepo with a NestJS server and an Angular front-end to test it with.
I opted to jump into creating a micropub
end-point and to defer worrying about IndieAuth authentication until later. Trying to start small, I began by looking at the minimal example of posting a new note.
Since NestJS uses DTO classes to define the structure of POST
end-points, I created:
export class Entry {
h: string;
content: string;
}
My micropub
end-point was then simply written as:
@Post('micropub')
postEntry(@Body() entry: Entry) {
return this.micropubService.postEntry(entry);
}
For now, my service just writes the entry
data out to console.log
.
Happily, NestJS takes care of parsing out the POST
request for either JSON or a application/x-www-form-urlencoded
encoded string, saving a fair amount of coding that I would otherwise have had to create.
Testing the end-point from an Angular client was also fairly easy. I threw together a simple Reactive form to input the content data, and called the NestJS end-point with both types of content:
// Post as application/x-www-form-urlencoded data
submitForm() {
const value = this.entryForm.value;
const params = new HttpParams()
.set('h', 'entry')
.set('content', value['content']);
this.http.post('/api/micropub', params).subscribe();
}
// Post as JSON data
submitJson() {
const value = this.entryForm.value;
const params = {
h: 'entry',
content: value['content'],
};
this.http.post('/api/micropub', params).subscribe();
}
My next challenge was to figure out how to send multiple values for a property. The Micropub specification says:
For x-www-form-urlencoded and multipart/form-data requests, Micropub supports an extension of the standard URL encoding that includes explicit indicators of multi-valued properties. Specifically, this means in order to send multiple values for a given property, you must append square brackets [] to the property name.
For example, to specify multiple values for the property “category”, the request would include category[]=foo&category[]=bar. 1
Sending multiple values in JSON is straightforward, but I had to experiment for quite a bit to figure out how to get Angular to structure a urlencoded string to match the specification. After several failed experiments, I finally came up with:
submitForm() {
const value = this.entryForm.value;
const params = new HttpParams()
.set('h', 'entry')
.set('content', value['content'])
.set('category[]', 'one')
.append('category[]', 'two');
this.http.post('/api/micropub', params).subscribe();
}
The lesson learned was to use append
to add a second category[]
parameter, since using set
a second time would overwrite the first parameter.
Back on the NestJS side, I added an optional array of strings for the category:
export class Entry {
h: string;
content: string;
category?: string[];
}
And it Just Worked(tm). NestJS is smart enough to parse category[]=one&category[]=two
into the category
array as expected: category: [ 'one', 'two' ]
.
The next steps will be:
- Figure out how to translate the other properties from the Vocabulary : Creating Objects section of the Micropub specification into a DTO
- Figure out how to use the same DTO class for Update, Delete, and File Upload messages
- Figure out what to do about the fact that the JSON Syntax actually says to use arrays for all values, which means the JSON used so far isn't correct
- Eventually, figure out how to implement IndieAuth authentication
- Finally, figure out how to publish to my Gitea server
Between the significantly different schema that Update and Delete use vs Create, and the JSON Syntax complication, I am having second (and third) thoughts about whether NestJS will be the best solution for the server after all . . . .